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