blob: d3bc3e66fbeee38140dd0c6ad6441da4b81a7187 [file] [log] [blame]
Luke Huang00b15f32019-01-04 19:56:29 +08001/*
2 * Copyright (C) 2019 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 static android.net.NetworkUtils.resNetworkQuery;
20import static android.net.NetworkUtils.resNetworkResult;
21import static android.net.NetworkUtils.resNetworkSend;
22import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
23import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
24
25import android.annotation.IntDef;
26import android.annotation.NonNull;
27import android.annotation.Nullable;
28import android.os.Handler;
29import android.os.MessageQueue;
30import android.system.ErrnoException;
31import android.util.Log;
32
33import java.io.FileDescriptor;
34import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
36import java.net.InetAddress;
37import java.net.UnknownHostException;
38import java.util.ArrayList;
39import java.util.List;
40import java.util.function.Consumer;
41
42
43/**
44 * Dns resolver class for asynchronous dns querying
45 *
Luke Huang33bfef32019-01-23 21:53:13 +080046 * Note that if a client sends a query with more than 1 record in the question section but
47 * the remote dns server does not support this, it may not respond at all, leading to a timeout.
48 *
Luke Huang00b15f32019-01-04 19:56:29 +080049 */
50public final class DnsResolver {
51 private static final String TAG = "DnsResolver";
52 private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
53 private static final int MAXPACKET = 8 * 1024;
54
55 @IntDef(prefix = { "CLASS_" }, value = {
56 CLASS_IN
57 })
58 @Retention(RetentionPolicy.SOURCE)
59 @interface QueryClass {}
60 public static final int CLASS_IN = 1;
61
62 @IntDef(prefix = { "TYPE_" }, value = {
63 TYPE_A,
64 TYPE_AAAA
65 })
66 @Retention(RetentionPolicy.SOURCE)
67 @interface QueryType {}
68 public static final int TYPE_A = 1;
69 public static final int TYPE_AAAA = 28;
70
71 @IntDef(prefix = { "FLAG_" }, value = {
72 FLAG_EMPTY,
73 FLAG_NO_RETRY,
74 FLAG_NO_CACHE_STORE,
75 FLAG_NO_CACHE_LOOKUP
76 })
77 @Retention(RetentionPolicy.SOURCE)
78 @interface QueryFlag {}
79 public static final int FLAG_EMPTY = 0;
80 public static final int FLAG_NO_RETRY = 1 << 0;
81 public static final int FLAG_NO_CACHE_STORE = 1 << 1;
82 public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
83
84 private static final int DNS_RAW_RESPONSE = 1;
85
86 private static final int NETID_UNSET = 0;
87
88 private static final DnsResolver sInstance = new DnsResolver();
89
90 /**
91 * listener for receiving raw answers
92 */
93 public interface RawAnswerListener {
94 /**
95 * {@code byte[]} is {@code null} if query timed out
96 */
97 void onAnswer(@Nullable byte[] answer);
98 }
99
100 /**
101 * listener for receiving parsed answers
102 */
103 public interface InetAddressAnswerListener {
104 /**
105 * Will be called exactly once with all the answers to the query.
106 * size of addresses will be zero if no available answer could be parsed.
107 */
108 void onAnswer(@NonNull List<InetAddress> addresses);
109 }
110
111 /**
112 * Get instance for DnsResolver
113 */
114 public static DnsResolver getInstance() {
115 return sInstance;
116 }
117
118 private DnsResolver() {}
119
120 /**
121 * Pass in a blob and corresponding setting,
122 * get a blob back asynchronously with the entire raw answer.
123 *
124 * @param network {@link Network} specifying which network for querying.
125 * {@code null} for query on default network.
126 * @param query blob message
127 * @param flags flags as a combination of the FLAGS_* constants
128 * @param handler {@link Handler} to specify the thread
129 * upon which the {@link RawAnswerListener} will be invoked.
130 * @param listener a {@link RawAnswerListener} which will be called to notify the caller
131 * of the result of dns query.
132 */
133 public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
134 @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
135 final FileDescriptor queryfd = resNetworkSend((network != null
136 ? network.netId : NETID_UNSET), query, query.length, flags);
137 registerFDListener(handler.getLooper().getQueue(), queryfd,
138 answerbuf -> listener.onAnswer(answerbuf));
139 }
140
141 /**
142 * Pass in a domain name and corresponding setting,
143 * get a blob back asynchronously with the entire raw answer.
144 *
145 * @param network {@link Network} specifying which network for querying.
146 * {@code null} for query on default network.
147 * @param domain domain name for querying
148 * @param nsClass dns class as one of the CLASS_* constants
149 * @param nsType dns resource record (RR) type as one of the TYPE_* constants
150 * @param flags flags as a combination of the FLAGS_* constants
151 * @param handler {@link Handler} to specify the thread
152 * upon which the {@link RawAnswerListener} will be invoked.
153 * @param listener a {@link RawAnswerListener} which will be called to notify the caller
154 * of the result of dns query.
155 */
156 public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
157 @QueryType int nsType, @QueryFlag int flags,
158 @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
159 final FileDescriptor queryfd = resNetworkQuery((network != null
160 ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
161 registerFDListener(handler.getLooper().getQueue(), queryfd,
162 answerbuf -> listener.onAnswer(answerbuf));
163 }
164
165 /**
166 * Pass in a domain name and corresponding setting,
167 * get back a set of InetAddresses asynchronously.
168 *
169 * @param network {@link Network} specifying which network for querying.
170 * {@code null} for query on default network.
171 * @param domain domain name for querying
172 * @param flags flags as a combination of the FLAGS_* constants
173 * @param handler {@link Handler} to specify the thread
174 * upon which the {@link InetAddressAnswerListener} will be invoked.
175 * @param listener an {@link InetAddressAnswerListener} which will be called to
176 * notify the caller of the result of dns query.
177 *
178 */
179 public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
180 @NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
181 throws ErrnoException {
182 final FileDescriptor v4fd = resNetworkQuery((network != null
183 ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
184 final FileDescriptor v6fd = resNetworkQuery((network != null
185 ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
186
187 final InetAddressAnswerAccumulator accmulator =
188 new InetAddressAnswerAccumulator(2, listener);
189 final Consumer<byte[]> consumer = answerbuf ->
190 accmulator.accumulate(parseAnswers(answerbuf));
191
192 registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
193 registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
194 }
195
196 private void registerFDListener(@NonNull MessageQueue queue,
197 @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
198 queue.addOnFileDescriptorEventListener(
199 queryfd,
200 FD_EVENTS,
201 (fd, events) -> {
202 byte[] answerbuf = null;
203 try {
204 // TODO: Implement result function in Java side instead of using JNI
205 // Because JNI method close fd prior than unregistering fd on
206 // event listener.
207 answerbuf = resNetworkResult(fd);
208 } catch (ErrnoException e) {
209 Log.e(TAG, "resNetworkResult:" + e.toString());
210 }
211 answerConsumer.accept(answerbuf);
212
213 // Unregister this fd listener
214 return 0;
215 });
216 }
217
218 private class DnsAddressAnswer extends DnsPacket {
219 private static final String TAG = "DnsResolver.DnsAddressAnswer";
220 private static final boolean DBG = false;
221
222 private final int mQueryType;
223
224 DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
225 super(data);
226 if ((mHeader.flags & (1 << 15)) == 0) {
227 throw new ParseException("Not an answer packet");
228 }
229 if (mHeader.rcode != 0) {
230 throw new ParseException("Response error, rcode:" + mHeader.rcode);
231 }
Luke Huang33bfef32019-01-23 21:53:13 +0800232 if (mHeader.getRecordCount(ANSECTION) == 0) {
Luke Huang00b15f32019-01-04 19:56:29 +0800233 throw new ParseException("No available answer");
234 }
Luke Huang33bfef32019-01-23 21:53:13 +0800235 if (mHeader.getRecordCount(QDSECTION) == 0) {
Luke Huang00b15f32019-01-04 19:56:29 +0800236 throw new ParseException("No question found");
237 }
Luke Huang33bfef32019-01-23 21:53:13 +0800238 // Expect only one question in question section.
239 mQueryType = mRecords[QDSECTION].get(0).nsType;
Luke Huang00b15f32019-01-04 19:56:29 +0800240 }
241
242 public @NonNull List<InetAddress> getAddresses() {
243 final List<InetAddress> results = new ArrayList<InetAddress>();
Luke Huang33bfef32019-01-23 21:53:13 +0800244 for (final DnsRecord ansSec : mRecords[ANSECTION]) {
Luke Huang00b15f32019-01-04 19:56:29 +0800245 // Only support A and AAAA, also ignore answers if query type != answer type.
246 int nsType = ansSec.nsType;
247 if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
248 continue;
249 }
250 try {
251 results.add(InetAddress.getByAddress(ansSec.getRR()));
252 } catch (UnknownHostException e) {
253 if (DBG) {
254 Log.w(TAG, "rr to address fail");
255 }
256 }
257 }
258 return results;
259 }
260 }
261
262 private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
263 try {
264 return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
265 } catch (DnsPacket.ParseException e) {
266 Log.e(TAG, "Parse answer fail " + e.getMessage());
267 return null;
268 }
269 }
270
271 private class InetAddressAnswerAccumulator {
272 private final List<InetAddress> mAllAnswers;
273 private final InetAddressAnswerListener mAnswerListener;
274 private final int mTargetAnswerCount;
275 private int mReceivedAnswerCount = 0;
276
277 InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
278 mTargetAnswerCount = size;
279 mAllAnswers = new ArrayList<>();
280 mAnswerListener = listener;
281 }
282
283 public void accumulate(@Nullable List<InetAddress> answer) {
284 if (null != answer) {
285 mAllAnswers.addAll(answer);
286 }
287 if (++mReceivedAnswerCount == mTargetAnswerCount) {
288 mAnswerListener.onAnswer(mAllAnswers);
289 }
290 }
291 }
292}