001/*
002 * Copyright 2015-2024 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    /**
054     * Construct this DNS server lookup mechanism.
055     *
056     * @param context an Android context.
057     */
058    public AndroidUsingLinkProperties(Context context) {
059        super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1);
060        connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
061    }
062
063    @Override
064    public boolean isAvailable() {
065        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
066    }
067
068    @TargetApi(Build.VERSION_CODES.M)
069    private List<String> getDnsServerAddressesOfActiveNetwork() {
070        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
071            return null;
072        }
073
074        // ConnectivityManager.getActiveNetwork() is API 23.
075        Network activeNetwork = connectivityManager.getActiveNetwork();
076        if (activeNetwork == null) {
077            return null;
078        }
079
080        LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork);
081        if (linkProperties == null) {
082            return null;
083        }
084
085        List<InetAddress> dnsServers = linkProperties.getDnsServers();
086        return toListOfStrings(dnsServers);
087    }
088
089    @Override
090    @TargetApi(21)
091    public List<String> getDnsServerAddresses() {
092        // First, try the API 23 approach using ConnectivityManager.getActiveNetwork().
093        List<String> servers = getDnsServerAddressesOfActiveNetwork();
094        if (servers != null) {
095            return servers;
096        }
097
098        Network[] networks = connectivityManager.getAllNetworks();
099        if (networks == null) {
100            return null;
101        }
102
103        servers = new ArrayList<>(networks.length * 2);
104        for (Network network : networks) {
105            LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
106            if (linkProperties == null) {
107                continue;
108            }
109
110            // Prioritize the DNS servers of links which have a default route
111            if (hasDefaultRoute(linkProperties)) {
112                servers.addAll(0, toListOfStrings(linkProperties.getDnsServers()));
113            } else {
114                servers.addAll(toListOfStrings(linkProperties.getDnsServers()));
115            }
116        }
117
118        if (servers.isEmpty()) {
119            return null;
120        }
121
122        return servers;
123    }
124
125    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
126    private static boolean hasDefaultRoute(LinkProperties linkProperties) {
127        for (RouteInfo route : linkProperties.getRoutes()) {
128            if (route.isDefaultRoute()) {
129                return true;
130            }
131        }
132        return false;
133    }
134
135}