001/*
002 * Copyright 2015-2020 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.dnsserverlookup.android21;
012
013import android.annotation.TargetApi;
014import android.content.Context;
015import android.net.ConnectivityManager;
016import android.net.LinkProperties;
017import android.net.Network;
018import android.net.RouteInfo;
019import android.os.Build;
020
021import java.net.InetAddress;
022import java.util.ArrayList;
023import java.util.List;
024
025import org.minidns.DnsClient;
026import org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism;
027import org.minidns.dnsserverlookup.AndroidUsingExec;
028
029/**
030 * A DNS server lookup mechanism using Android's Link Properties method available on Android API 21 or higher. Use
031 * {@link #setup(Context)} to setup this mechanism.
032 * <p>
033 * Requires the ACCESS_NETWORK_STATE permission.
034 * </p>
035 */
036public class AndroidUsingLinkProperties extends AbstractDnsServerLookupMechanism {
037
038    private final ConnectivityManager connectivityManager;
039
040    /**
041     * Setup this DNS server lookup mechanism. You need to invoke this method only once, ideally before you do your
042     * first DNS lookup.
043     *
044     * @param context a Context instance.
045     * @return the instance of the newly setup mechanism
046     */
047    public static AndroidUsingLinkProperties setup(Context context) {
048        AndroidUsingLinkProperties androidUsingLinkProperties = new AndroidUsingLinkProperties(context);
049        DnsClient.addDnsServerLookupMechanism(androidUsingLinkProperties);
050        return androidUsingLinkProperties;
051    }
052
053    public AndroidUsingLinkProperties(Context context) {
054        super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1);
055        connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
056    }
057
058    @Override
059    public boolean isAvailable() {
060        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
061    }
062
063    @TargetApi(Build.VERSION_CODES.M)
064    private List<String> getDnsServerAddressesOfActiveNetwork() {
065        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
066            return null;
067        }
068
069        // ConnectivityManager.getActiveNetwork() is API 23.
070        Network activeNetwork = connectivityManager.getActiveNetwork();
071        if (activeNetwork == null) {
072            return null;
073        }
074
075        LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork);
076        if (linkProperties == null) {
077            return null;
078        }
079
080        List<InetAddress> dnsServers = linkProperties.getDnsServers();
081        return toListOfStrings(dnsServers);
082    }
083
084    @Override
085    @TargetApi(21)
086    public List<String> getDnsServerAddresses() {
087        // First, try the API 23 approach using ConnectivityManager.getActiveNetwork().
088        List<String> servers = getDnsServerAddressesOfActiveNetwork();
089        if (servers != null) {
090            return servers;
091        }
092
093        Network[] networks = connectivityManager.getAllNetworks();
094        if (networks == null) {
095            return null;
096        }
097
098        servers = new ArrayList<>(networks.length * 2);
099        for (Network network : networks) {
100            LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
101            if (linkProperties == null) {
102                continue;
103            }
104
105            // Prioritize the DNS servers of links which have a default route
106            if (hasDefaultRoute(linkProperties)) {
107                servers.addAll(0, toListOfStrings(linkProperties.getDnsServers()));
108            } else {
109                servers.addAll(toListOfStrings(linkProperties.getDnsServers()));
110            }
111        }
112
113        if (servers.isEmpty()) {
114            return null;
115        }
116
117        return servers;
118    }
119
120    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
121    private static boolean hasDefaultRoute(LinkProperties linkProperties) {
122        for (RouteInfo route : linkProperties.getRoutes()) {
123            if (route.isDefaultRoute()) {
124                return true;
125            }
126        }
127        return false;
128    }
129
130}