Luke Huang | 00b15f3 | 2019-01-04 19:56:29 +0800 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package android.net; |
| 18 | |
| 19 | import static android.net.NetworkUtils.resNetworkQuery; |
| 20 | import static android.net.NetworkUtils.resNetworkResult; |
| 21 | import static android.net.NetworkUtils.resNetworkSend; |
| 22 | import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; |
| 23 | import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; |
| 24 | |
| 25 | import android.annotation.IntDef; |
| 26 | import android.annotation.NonNull; |
| 27 | import android.annotation.Nullable; |
| 28 | import android.os.Handler; |
| 29 | import android.os.MessageQueue; |
| 30 | import android.system.ErrnoException; |
| 31 | import android.util.Log; |
| 32 | |
| 33 | import java.io.FileDescriptor; |
| 34 | import java.lang.annotation.Retention; |
| 35 | import java.lang.annotation.RetentionPolicy; |
| 36 | import java.net.InetAddress; |
| 37 | import java.net.UnknownHostException; |
| 38 | import java.util.ArrayList; |
| 39 | import java.util.List; |
| 40 | import java.util.function.Consumer; |
| 41 | |
| 42 | |
| 43 | /** |
| 44 | * Dns resolver class for asynchronous dns querying |
| 45 | * |
| 46 | */ |
| 47 | public 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 | } |