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 
DnsArg(InetAddress d, int s)113         DnsArg(InetAddress d, int s) {
114             dns = d;
115             seq = s;
116         }
117     }
118 
DnsPinger(Context context, String TAG, Looper looper, Handler target, int connectionType)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
handleMessage(Message msg)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      */
getDnsList()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.getDnsServers();
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      */
pingDnsAsync(InetAddress dns, int timeout, int delay)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 
cancelPings()274     public void cancelPings() {
275         mCurrentToken.incrementAndGet();
276         obtainMessage(ACTION_CANCEL_ALL_PINGS).sendToTarget();
277     }
278 
sendResponse(int internalId, int externalId, int responseVal)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 
getCurrentLinkProperties()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 
getDefaultDns()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 
log(String s)327     private void log(String s) {
328         Log.d(TAG, s);
329     }
330 
loge(String s)331     private void loge(String s) {
332         Log.e(TAG, s);
333     }
334 }
335