blob: 5cfda7ec9965b689bf9ee0776b0062f076118628 [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>
41 * TODO : More general API. Wifi is currently hard coded
42 * 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;
61
62 private String TAG;
63
64 public DnsPinger(String TAG, Context context) {
65 mContext = context;
66 mContentResolver = context.getContentResolver();
67 this.TAG = TAG;
68 }
69
70 /**
71 * Gets the first DNS of the current Wifi AP.
72 * @return The first DNS of the current AP.
73 */
74 public InetAddress getDns() {
75 if (mConnectivityManager == null) {
76 mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
77 Context.CONNECTIVITY_SERVICE);
78 }
79
80 LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
81 ConnectivityManager.TYPE_WIFI);
82 if (linkProperties == null)
83 return null;
84
85 Collection<InetAddress> dnses = linkProperties.getDnses();
86 if (dnses == null || dnses.size() == 0)
87 return null;
88
89 return dnses.iterator().next();
90 }
91
92 /**
93 * @return time to response. Negative value on error.
94 */
95 public long pingDns(InetAddress dnsAddress, int timeout) {
96 DatagramSocket socket = null;
97 try {
98 socket = new DatagramSocket();
99
100 // Set some socket properties
101 socket.setSoTimeout(timeout);
102
103 byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
104 fillQuery(buf);
105
106 // Send the DNS query
107
108 DatagramPacket packet = new DatagramPacket(buf,
109 buf.length, dnsAddress, DNS_PORT);
110 long start = SystemClock.elapsedRealtime();
111 socket.send(packet);
112
113 // Wait for reply (blocks for the above timeout)
114 DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
115 socket.receive(replyPacket);
116
117 // If a timeout occurred, an exception would have been thrown. We
118 // got a reply!
119 return SystemClock.elapsedRealtime() - start;
120
121 } catch (SocketTimeoutException e) {
122 // Squelch this exception.
123 return -1;
124 } catch (Exception e) {
125 if (V) {
126 Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
127 }
128 return -2;
129 } finally {
130 if (socket != null) {
131 socket.close();
132 }
133 }
134
135 }
136
137 private static void fillQuery(byte[] buf) {
138
139 /*
140 * See RFC2929 (though the bit tables in there are misleading for us.
141 * For example, the recursion desired bit is the 0th bit for us, but
142 * looking there it would appear as the 7th bit of the byte
143 */
144
145 // Make sure it's all zeroed out
146 for (int i = 0; i < buf.length; i++)
147 buf[i] = 0;
148
149 // Form a query for www.android.com
150
151 // [0-1] bytes are an ID, generate random ID for this query
152 buf[0] = (byte) sRandom.nextInt(256);
153 buf[1] = (byte) sRandom.nextInt(256);
154
155 // [2-3] bytes are for flags.
156 buf[2] = 1; // Recursion desired
157
158 // [4-5] bytes are for the query count
159 buf[5] = 1; // One query
160
161 // [6-7] [8-9] [10-11] are all counts of other fields we don't use
162
163 // [12-15] for www
164 writeString(buf, 12, "www");
165
166 // [16-23] for android
167 writeString(buf, 16, "android");
168
169 // [24-27] for com
170 writeString(buf, 24, "com");
171
172 // [29-30] bytes are for QTYPE, set to 1
173 buf[30] = 1;
174
175 // [31-32] bytes are for QCLASS, set to 1
176 buf[32] = 1;
177 }
178
179 private static void writeString(byte[] buf, int startPos, String string) {
180 int pos = startPos;
181
182 // Write the length first
183 buf[pos++] = (byte) string.length();
184 for (int i = 0; i < string.length(); i++) {
185 buf[pos++] = (byte) string.charAt(i);
186 }
187 }
188}