blob: 05de53a2810cd59e16a1274de454296661a26cc1 [file] [log] [blame]
Isaac Levybc7dfb52011-06-06 15:34:01 -07001/*
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 com.android.server;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.net.ConnectivityManager;
22import android.net.LinkProperties;
23import android.os.SystemClock;
24import android.util.Slog;
25
26import java.net.DatagramPacket;
27import java.net.DatagramSocket;
28import java.net.InetAddress;
29import java.net.SocketTimeoutException;
30import java.util.Collection;
31import java.util.Random;
32
33/**
34 * Performs a simple DNS "ping" by sending a "server status" query packet to the
35 * DNS server. As long as the server replies, we consider it a success.
36 * <p>
37 * We do not use a simple hostname lookup because that could be cached and the
38 * API may not differentiate between a time out and a failure lookup (which we
39 * really care about).
40 * <p>
Isaac Levyb1ef2922011-06-27 10:02:50 -070041 * TODO : More general API. Socket does not bind to specified connection type
Isaac Levybc7dfb52011-06-06 15:34:01 -070042 * TODO : Choice of DNS query location - current looks up www.android.com
43 *
44 * @hide
45 */
46public final class DnsPinger {
47 private static final boolean V = true;
48
49 /** Number of bytes for the query */
50 private static final int DNS_QUERY_BASE_SIZE = 33;
51
52 /** The DNS port */
53 private static final int DNS_PORT = 53;
54
55 /** Used to generate IDs */
56 private static Random sRandom = new Random();
57
58 private ConnectivityManager mConnectivityManager = null;
59 private ContentResolver mContentResolver;
60 private Context mContext;
Isaac Levyb1ef2922011-06-27 10:02:50 -070061 private int mConnectionType;
Isaac Levybc7dfb52011-06-06 15:34:01 -070062
63 private String TAG;
64
Isaac Levyb1ef2922011-06-27 10:02:50 -070065
66 /**
67 * @param connectionType The connection type from @link {@link ConnectivityManager}
68 */
69 public DnsPinger(String TAG, Context context, int connectionType) {
Isaac Levybc7dfb52011-06-06 15:34:01 -070070 mContext = context;
71 mContentResolver = context.getContentResolver();
Isaac Levyb1ef2922011-06-27 10:02:50 -070072 mConnectionType = connectionType;
Isaac Levybc7dfb52011-06-06 15:34:01 -070073 this.TAG = TAG;
74 }
75
76 /**
Isaac Levyb1ef2922011-06-27 10:02:50 -070077 * @return The first DNS in the link properties of the specified connection type
Isaac Levybc7dfb52011-06-06 15:34:01 -070078 */
79 public InetAddress getDns() {
Isaac Levyb1ef2922011-06-27 10:02:50 -070080 LinkProperties linkProperties = getCurLinkProperties();
Isaac Levybc7dfb52011-06-06 15:34:01 -070081 if (linkProperties == null)
82 return null;
83
84 Collection<InetAddress> dnses = linkProperties.getDnses();
85 if (dnses == null || dnses.size() == 0)
86 return null;
87
88 return dnses.iterator().next();
89 }
90
Isaac Levyb1ef2922011-06-27 10:02:50 -070091 private LinkProperties getCurLinkProperties() {
92 if (mConnectivityManager == null) {
93 mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
94 Context.CONNECTIVITY_SERVICE);
95 }
96 return mConnectivityManager.getLinkProperties(mConnectionType);
97 }
98
Isaac Levybc7dfb52011-06-06 15:34:01 -070099 /**
100 * @return time to response. Negative value on error.
101 */
102 public long pingDns(InetAddress dnsAddress, int timeout) {
103 DatagramSocket socket = null;
104 try {
105 socket = new DatagramSocket();
106
107 // Set some socket properties
108 socket.setSoTimeout(timeout);
109
110 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
111 fillQuery(buf);
112
113 // Send the DNS query
114
115 DatagramPacket packet = new DatagramPacket(buf,
116 buf.length, dnsAddress, DNS_PORT);
117 long start = SystemClock.elapsedRealtime();
118 socket.send(packet);
119
120 // Wait for reply (blocks for the above timeout)
121 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
122 socket.receive(replyPacket);
123
124 // If a timeout occurred, an exception would have been thrown. We
125 // got a reply!
126 return SystemClock.elapsedRealtime() - start;
127
128 } catch (SocketTimeoutException e) {
129 // Squelch this exception.
130 return -1;
131 } catch (Exception e) {
132 if (V) {
133 Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
134 }
135 return -2;
136 } finally {
137 if (socket != null) {
138 socket.close();
139 }
140 }
141
142 }
143
144 private static void fillQuery(byte[] buf) {
145
146 /*
147 * See RFC2929 (though the bit tables in there are misleading for us.
148 * For example, the recursion desired bit is the 0th bit for us, but
149 * looking there it would appear as the 7th bit of the byte
150 */
151
152 // Make sure it's all zeroed out
153 for (int i = 0; i < buf.length; i++)
154 buf[i] = 0;
155
156 // Form a query for www.android.com
157
158 // [0-1] bytes are an ID, generate random ID for this query
159 buf[0] = (byte) sRandom.nextInt(256);
160 buf[1] = (byte) sRandom.nextInt(256);
161
162 // [2-3] bytes are for flags.
163 buf[2] = 1; // Recursion desired
164
165 // [4-5] bytes are for the query count
166 buf[5] = 1; // One query
167
168 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
169
170 // [12-15] for www
171 writeString(buf, 12, "www");
172
173 // [16-23] for android
174 writeString(buf, 16, "android");
175
176 // [24-27] for com
177 writeString(buf, 24, "com");
178
179 // [29-30] bytes are for QTYPE, set to 1
180 buf[30] = 1;
181
182 // [31-32] bytes are for QCLASS, set to 1
183 buf[32] = 1;
184 }
185
186 private static void writeString(byte[] buf, int startPos, String string) {
187 int pos = startPos;
188
189 // Write the length first
190 buf[pos++] = (byte) string.length();
191 for (int i = 0; i < string.length(); i++) {
192 buf[pos++] = (byte) string.charAt(i);
193 }
194 }
195}