DnsPinger.java revision a7bc1135c270fd4a84ab7ad45b7194e9b580300e
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.net.ConnectivityManager; 21import android.net.LinkProperties; 22import android.net.NetworkUtils; 23import android.os.SystemClock; 24import android.provider.Settings; 25import android.util.Slog; 26 27import java.net.DatagramPacket; 28import java.net.DatagramSocket; 29import java.net.InetAddress; 30import java.net.NetworkInterface; 31import java.net.SocketTimeoutException; 32import java.util.Collection; 33import java.util.Random; 34 35/** 36 * Performs a simple DNS "ping" by sending a "server status" query packet to the 37 * DNS server. As long as the server replies, we consider it a success. 38 * <p> 39 * We do not use a simple hostname lookup because that could be cached and the 40 * API may not differentiate between a time out and a failure lookup (which we 41 * really care about). 42 * <p> 43 * TODO : More general API. Socket does not bind to specified connection type 44 * TODO : Choice of DNS query location - current looks up www.android.com 45 * 46 * @hide 47 */ 48public final class DnsPinger { 49 private static final boolean V = true; 50 51 /** Number of bytes for the query */ 52 private static final int DNS_QUERY_BASE_SIZE = 32; 53 54 /** The DNS port */ 55 private static final int DNS_PORT = 53; 56 57 /** Used to generate IDs */ 58 private static Random sRandom = new Random(); 59 60 private ConnectivityManager mConnectivityManager = null; 61 private Context mContext; 62 private int mConnectionType; 63 private InetAddress mDefaultDns; 64 65 private String TAG; 66 67 /** 68 * @param connectionType The connection type from {@link ConnectivityManager} 69 */ 70 public DnsPinger(String TAG, Context context, int connectionType) { 71 mContext = context; 72 mConnectionType = connectionType; 73 if (!ConnectivityManager.isNetworkTypeValid(connectionType)) { 74 Slog.e(TAG, "Invalid connectionType in constructor: " + connectionType); 75 } 76 this.TAG = TAG; 77 78 mDefaultDns = getDefaultDns(); 79 } 80 81 /** 82 * @return The first DNS in the link properties of the specified connection 83 * type or the default system DNS if the link properties has null 84 * dns set. Should not be null. 85 */ 86 public InetAddress getDns() { 87 LinkProperties curLinkProps = getCurrentLinkProperties(); 88 if (curLinkProps == null) { 89 Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!"); 90 return mDefaultDns; 91 } 92 93 Collection<InetAddress> dnses = curLinkProps.getDnses(); 94 if (dnses == null || dnses.size() == 0) { 95 Slog.v(TAG, "getDns::LinkProps has null dns - returning default"); 96 return mDefaultDns; 97 } 98 99 return dnses.iterator().next(); 100 } 101 102 private LinkProperties getCurrentLinkProperties() { 103 if (mConnectivityManager == null) { 104 mConnectivityManager = (ConnectivityManager) mContext.getSystemService( 105 Context.CONNECTIVITY_SERVICE); 106 } 107 108 return mConnectivityManager.getLinkProperties(mConnectionType); 109 } 110 111 private InetAddress getDefaultDns() { 112 String dns = Settings.Secure.getString(mContext.getContentResolver(), 113 Settings.Secure.DEFAULT_DNS_SERVER); 114 if (dns == null || dns.length() == 0) { 115 dns = mContext.getResources().getString( 116 com.android.internal.R.string.config_default_dns_server); 117 } 118 try { 119 return NetworkUtils.numericToInetAddress(dns); 120 } catch (IllegalArgumentException e) { 121 Slog.w(TAG, "getDefaultDns::malformed default dns address"); 122 return null; 123 } 124 } 125 126 /** 127 * @return time to response. Negative value on error. 128 */ 129 public long pingDns(InetAddress dnsAddress, int timeout) { 130 DatagramSocket socket = null; 131 try { 132 socket = new DatagramSocket(); 133 134 // Set some socket properties 135 socket.setSoTimeout(timeout); 136 137 // Try to bind but continue ping if bind fails 138 try { 139 socket.setNetworkInterface(NetworkInterface.getByName( 140 getCurrentLinkProperties().getInterfaceName())); 141 } catch (Exception e) { 142 Slog.d(TAG,"pingDns::Error binding to socket", e); 143 } 144 145 byte[] buf = constructQuery(); 146 147 // Send the DNS query 148 149 DatagramPacket packet = new DatagramPacket(buf, 150 buf.length, dnsAddress, DNS_PORT); 151 long start = SystemClock.elapsedRealtime(); 152 socket.send(packet); 153 154 // Wait for reply (blocks for the above timeout) 155 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length); 156 socket.receive(replyPacket); 157 158 // If a timeout occurred, an exception would have been thrown. We 159 // got a reply! 160 return SystemClock.elapsedRealtime() - start; 161 162 } catch (SocketTimeoutException e) { 163 // Squelch this exception. 164 return -1; 165 } catch (Exception e) { 166 if (V) { 167 Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e); 168 } 169 return -2; 170 } finally { 171 if (socket != null) { 172 socket.close(); 173 } 174 } 175 176 } 177 178 /** 179 * @return google.com DNS query packet 180 */ 181 private static byte[] constructQuery() { 182 byte[] buf = new byte[DNS_QUERY_BASE_SIZE]; 183 184 // [0-1] bytes are an ID, generate random ID for this query 185 buf[0] = (byte) sRandom.nextInt(256); 186 buf[1] = (byte) sRandom.nextInt(256); 187 188 // [2-3] bytes are for flags. 189 buf[2] = 0x01; // Recursion desired 190 191 // [4-5] bytes are for number of queries (QCOUNT) 192 buf[5] = 0x01; 193 194 // [6-7] [8-9] [10-11] are all counts of other fields we don't use 195 196 // [12-15] for www 197 writeString(buf, 12, "www"); 198 199 // [16-22] for google 200 writeString(buf, 16, "google"); 201 202 // [23-26] for com 203 writeString(buf, 23, "com"); 204 205 // [27] is a null byte terminator byte for the url 206 207 // [28-29] bytes are for QTYPE, set to 1 = A (host address) 208 buf[29] = 0x01; 209 210 // [30-31] bytes are for QCLASS, set to 1 = IN (internet) 211 buf[31] = 0x01; 212 213 return buf; 214 } 215 216 /** 217 * Writes the string's length and its contents to the buffer 218 */ 219 private static void writeString(byte[] buf, int startPos, String string) { 220 int pos = startPos; 221 222 // Write the length first 223 buf[pos++] = (byte) string.length(); 224 for (int i = 0; i < string.length(); i++) { 225 buf[pos++] = (byte) string.charAt(i); 226 } 227 } 228} 229