Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/util/net/TwidereDns.java

318 lines
12 KiB
Java

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util.net;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.TimingLogger;
import org.apache.commons.lang3.StringUtils;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.Address;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import javax.inject.Singleton;
import okhttp3.Dns;
@Singleton
public class TwidereDns implements Dns, Constants {
private static final String RESOLVER_LOGTAG = "TwidereDns";
private final SharedPreferences mHostMapping;
private final SharedPreferencesWrapper mPreferences;
private final SystemHosts mSystemHosts;
private Resolver mResolver;
private boolean mUseResolver;
public TwidereDns(final Context context, SharedPreferencesWrapper preferences) {
mHostMapping = SharedPreferencesWrapper.getInstance(context, HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mSystemHosts = new SystemHosts();
mPreferences = preferences;
reloadDnsSettings();
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
try {
return resolveInternal(hostname, hostname, 0, mUseResolver);
} catch (IOException e) {
if (e instanceof UnknownHostException) throw (UnknownHostException) e;
throw new UnknownHostException("Unable to resolve address " + e.getMessage());
} catch (SecurityException e) {
throw new UnknownHostException("Security exception" + e.getMessage());
}
}
public List<InetAddress> lookupResolver(String hostname) throws UnknownHostException {
try {
return resolveInternal(hostname, hostname, 0, true);
} catch (IOException e) {
if (e instanceof UnknownHostException) throw (UnknownHostException) e;
throw new UnknownHostException("Unable to resolve address " + e.getMessage());
} catch (SecurityException e) {
throw new UnknownHostException("Security exception" + e.getMessage());
}
}
public void reloadDnsSettings() {
mResolver = null;
mUseResolver = mPreferences.getBoolean(KEY_BUILTIN_DNS_RESOLVER);
}
@NonNull
private List<InetAddress> resolveInternal(final String originalHost, final String host, final int depth,
final boolean useResolver) throws IOException, SecurityException {
final TimingLogger logger = new TimingLogger(RESOLVER_LOGTAG, "resolve");
// Return if host is an address
final List<InetAddress> fromAddressString = fromAddressString(originalHost, host);
if (fromAddressString != null) {
addLogSplit(logger, host, "valid ip address", depth);
dumpLog(logger, fromAddressString);
return fromAddressString;
}
// Load from custom mapping
addLogSplit(logger, host, "start custom mapping resolve", depth);
final List<InetAddress> fromMapping = getFromMapping(host);
addLogSplit(logger, host, "end custom mapping resolve", depth);
if (fromMapping != null) {
dumpLog(logger, fromMapping);
return fromMapping;
}
if (useResolver) {
// Load from /etc/hosts, since Dnsjava doesn't support hosts entry lookup
addLogSplit(logger, host, "start /etc/hosts resolve", depth);
final List<InetAddress> fromSystemHosts = fromSystemHosts(host);
addLogSplit(logger, host, "end /etc/hosts resolve", depth);
if (fromSystemHosts != null) {
dumpLog(logger, fromSystemHosts);
return fromSystemHosts;
}
// Use DNS resolver
addLogSplit(logger, host, "start resolver resolve", depth);
final List<InetAddress> fromResolver = fromResolver(originalHost, host);
addLogSplit(logger, host, "end resolver resolve", depth);
if (fromResolver != null) {
dumpLog(logger, fromResolver);
return fromResolver;
}
}
addLogSplit(logger, host, "start system default resolve", depth);
final List<InetAddress> fromDefault = Arrays.asList(InetAddress.getAllByName(host));
addLogSplit(logger, host, "end system default resolve", depth);
dumpLog(logger, fromDefault);
return fromDefault;
}
private void dumpLog(final TimingLogger logger, @NonNull List<InetAddress> addresses) {
if (BuildConfig.DEBUG) return;
Log.v(RESOLVER_LOGTAG, "Resolved " + addresses);
logger.dumpToLog();
}
private void addLogSplit(final TimingLogger logger, String host, String message, int depth) {
if (BuildConfig.DEBUG) return;
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(">");
}
sb.append(" ");
sb.append(host);
sb.append(": ");
sb.append(message);
logger.addSplit(sb.toString());
}
private List<InetAddress> fromSystemHosts(String host) {
try {
return mSystemHosts.resolve(host);
} catch (IOException e) {
return null;
}
}
@Nullable
private List<InetAddress> fromResolver(String originalHost, String host) throws IOException {
final Resolver resolver = getResolver();
final Record[] records = lookupHostName(resolver, host, true);
final List<InetAddress> addrs = new ArrayList<>(records.length);
for (Record record : records) {
addrs.add(addrFromRecord(originalHost, record));
}
if (addrs.isEmpty()) return null;
return addrs;
}
@Nullable
private List<InetAddress> getFromMapping(final String host) throws UnknownHostException {
return getFromMappingInternal(host, host, false);
}
@Nullable
private List<InetAddress> getFromMappingInternal(String host, String origHost, boolean checkRecursive) throws UnknownHostException {
if (checkRecursive && hostMatches(host, origHost)) {
// Recursive resolution, stop this call
return null;
}
for (final Entry<String, ?> entry : mHostMapping.getAll().entrySet()) {
if (hostMatches(host, entry.getKey())) {
final String value = (String) entry.getValue();
final InetAddress resolved = getResolvedIPAddress(origHost, value);
if (resolved == null) {
// Maybe another hostname
return getFromMappingInternal(value, origHost, true);
}
return Collections.singletonList(resolved);
}
}
return null;
}
@NonNull
private Resolver getResolver() throws IOException {
if (mResolver != null) return mResolver;
final boolean tcp = mPreferences.getBoolean(KEY_TCP_DNS_QUERY, false);
final String address = mPreferences.getString(KEY_DNS_SERVER, null);
final SimpleResolver resolver;
if (!TextUtils.isEmpty(address) && isValidIpAddress(address)) {
resolver = new SimpleResolver(address);
} else {
resolver = new SimpleResolver();
}
resolver.setTCP(tcp);
return mResolver = resolver;
}
private static boolean hostMatches(final String host, final String rule) {
if (rule == null || host == null) return false;
if (rule.startsWith(".")) return StringUtils.endsWithIgnoreCase(host, rule);
return host.equalsIgnoreCase(rule);
}
@Nullable
private List<InetAddress> fromAddressString(String host, String address)
throws UnknownHostException {
final InetAddress resolved = getResolvedIPAddress(host, address);
if (resolved == null) return null;
return Collections.singletonList(resolved);
}
public static InetAddress getResolvedIPAddress(@NonNull final String host,
@NonNull final String address)
throws UnknownHostException {
byte[] bytes;
bytes = Address.toByteArray(address, Address.IPv4);
if (bytes != null)
return InetAddress.getByAddress(host, bytes);
bytes = Address.toByteArray(address, Address.IPv6);
if (bytes != null)
return InetAddress.getByAddress(host, bytes);
return null;
}
private static int getInetAddressType(@NonNull final String address) {
byte[] bytes;
bytes = Address.toByteArray(address, Address.IPv4);
if (bytes != null)
return Address.IPv4;
bytes = Address.toByteArray(address, Address.IPv6);
if (bytes != null)
return Address.IPv6;
return 0;
}
public static boolean isValidIpAddress(@NonNull final String address) {
return getInetAddressType(address) != 0;
}
private static Record[] lookupHostName(Resolver resolver, String name, boolean all) throws UnknownHostException {
try {
Lookup lookup = newLookup(resolver, name, Type.A);
Record[] a = lookup.run();
if (a == null) {
if (lookup.getResult() == Lookup.TYPE_NOT_FOUND) {
Record[] aaaa = newLookup(resolver, name, Type.AAAA).run();
if (aaaa != null)
return aaaa;
}
throw new UnknownHostException("unknown host");
}
if (!all)
return a;
Record[] aaaa = newLookup(resolver, name, Type.AAAA).run();
if (aaaa == null)
return a;
Record[] merged = new Record[a.length + aaaa.length];
System.arraycopy(a, 0, merged, 0, a.length);
System.arraycopy(aaaa, 0, merged, a.length, aaaa.length);
return merged;
} catch (TextParseException e) {
throw new UnknownHostException("invalid name");
}
}
private static Lookup newLookup(Resolver resolver, String name, int type) throws TextParseException {
final Lookup lookup = new Lookup(name, type);
lookup.setResolver(resolver);
return lookup;
}
private static InetAddress addrFromRecord(String name, Record r) throws UnknownHostException {
InetAddress addr;
if (r instanceof ARecord) {
addr = ((ARecord) r).getAddress();
} else {
addr = ((AAAARecord) r).getAddress();
}
return InetAddress.getByAddress(name, addr.getAddress());
}
}