31

I have a third party VPN app on my non-rooted Android 4.4 device, and want to write a background service to monitor the VPN connection and alert the user if the VPN connection has been broken.

Is there a way to do this? I couldn't find any way using the VPNService API.

Thanks -D

Dan Largo
  • 1,075
  • 3
  • 11
  • 25

9 Answers9

29

Using NetworkCapabilities worked for me. You have to loop over all existing networks and check which has VPN_TRANSPORT

ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
Network[] networks = cm.getAllNetworks();

Log.i(TAG, "Network count: " + networks.length);
for(int i = 0; i < networks.length; i++) {

  NetworkCapabilities caps = cm.getNetworkCapabilities(networks[i]);

  Log.i(TAG, "Network " + i + ": " + networks[i].toString());
  Log.i(TAG, "VPN transport is: " + caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN));
  Log.i(TAG, "NOT_VPN capability is: " + caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN));

}
Isaac Madwed
  • 986
  • 9
  • 23
  • 1
    `ConnectivityManager.getAllNetworks()` is only available in **API level 21 Lollipop**. @Isaac Madwed Do you now a way to do this check on _API Level 15 Ice Cream Sandwich_ and above ? – vovahost Feb 26 '16 at 08:44
  • 1
    Unfortunately not @vovahost. My team has interest in supporting those versions of android as well, so I will edit my post if we figure something out. – Isaac Madwed Feb 29 '16 at 14:50
  • 1
    How do you know which one is active? – NickF Apr 03 '16 at 15:46
  • 1
    @NickF You can use `getActiveNetwork()` on API 23+. On API 21 & 22, I consider a VPN connected if any network has a VPN. – Louis CAD Aug 29 '17 at 14:52
22

An alternative to check if the active Network is using VPN:

Network activeNetwork = connectivityManager.getActiveNetwork();
NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(activeNetwork);
boolean vpnInUse = caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Almer
  • 1,139
  • 1
  • 12
  • 14
  • 4
    This is what actually answers the question. Instead of looping through all networks, the active network should be checked. The point is to know if the device is actually using a VPN connecting actively – Xid Oct 20 '20 at 09:28
  • That's the right answer. – Ali Has Feb 11 '22 at 19:19
20

This is works for me: Tested from API 16 to 23:

List<String> networkList = new ArrayList<>();
try {
    for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
        if (networkInterface.isUp())
            networkList.add(networkInterface.getName());
    }
} catch (Exception ex) {
    Timber.d("isVpnUsing Network List didn't received");
}

return networkList.contains("tun0");

P.S. Also it can be another networkInterface with name "ppp0", but in my tests with different VPN apps it's always was "tun0"

ataravati
  • 8,891
  • 9
  • 57
  • 89
AndZp
  • 209
  • 2
  • 5
  • 5
    This changes. For instance, when using OpenVPN, it'll say *tunX*, where X is sometimes an incremented id. When using PPTP, the interface used is *pptpX*, where X, again, is an id starting from 0. – TheRealChx101 Dec 22 '16 at 12:19
17

Late answer, tested on Android 16 <-> 29:

public boolean vpn() {
    String iface = "";
    try {
        for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
            if (networkInterface.isUp())
                iface = networkInterface.getName();
                Log.d("DEBUG", "IFACE NAME: " + iface);
            if ( iface.contains("tun") || iface.contains("ppp") || iface.contains("pptp")) {
                return true;
            }
        }
    } catch (SocketException e1) {
        e1.printStackTrace();
    }

    return false;
}

The above doesn't work with wireguard because the interface name can be anything (normally the connection's name).

Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
4

I wrote a helper function for myself that works API 21 and above.

public static boolean vpnActive(Context context){
        
    // This method doesn't work below API 21
    if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        return false;

    boolean vpnInUse = false;

    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        Network activeNetwork = connectivityManager.getActiveNetwork();
        NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(activeNetwork);

        return caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
    }

    Network[] networks = connectivityManager.getAllNetworks();

    for (int i = 0; i < networks.length; i++) {
        NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(networks[i]);
        if (caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
            vpnInUse = true;
            break;
        }
    }

    return vpnInUse;
}
Devin Brown
  • 1,112
  • 10
  • 17
touhid udoy
  • 4,005
  • 2
  • 18
  • 31
4

Here is kotlin coroutines flow solution

val isVpnActiveFlow = callbackFlow {
    val connectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
    if (connectivityManager == null) {
        channel.close(IllegalStateException("connectivity manager is null"))
        return@callbackFlow
    } else {
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                channel.trySend(true)
            }
            override fun onLost(network: Network) {
               channel.trySend(false)
            }
        }
        connectivityManager.registerNetworkCallback(

            //I have to investigate on this builder!
            NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                    .build(),
            callback
        )
        awaitClose {
            connectivityManager.unregisterNetworkCallback(callback)
        }
    }
}
Devin Brown
  • 1,112
  • 10
  • 17
  • Answer needs supporting information Your answer could be improved with additional supporting information. Please [edit](https://stackoverflow.com/review/suggested-edits/34358786) to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](https://stackoverflow.com/help/how-to-answer). – moken May 14 '23 at 11:03
0

Write a little app that hits an internal page or pings an internal site...

HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(internal_url);
HttpResponse response = client.execute(request);

String html = "";
InputStream in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder str = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null)
{
    str.append(line);
}
in.close();
html = str.toString();

or

String str = "";
try 
{
    Process process = Runtime.getRuntime().exec(
            "/system/bin/ping -c 8 " + internal_url);
    BufferedReader reader = new BufferedReader(new InputStreamReader(
            process.getInputStream()));
    int i;
    char[] buffer = new char[4096];
    StringBuffer output = new StringBuffer();
    while ((i = reader.read(buffer)) > 0)
        output.append(buffer, 0, i);
    reader.close();


    str = output.toString();

} 
catch (IOException e)
{

    e.printStackTrace();
}
return str;
Stuart Siegler
  • 1,686
  • 4
  • 30
  • 38
  • Thanks, Stuart. This will work, but I'm looking for something that doesn't require pinging a backend service. Are there any system wide broadcasts that Android sends based on VPN state? – Dan Largo Feb 07 '15 at 20:04
  • 1
    @user714122 the vpnService prepare() (http://developer.android.com/reference/android/net/VpnService.html) might be able to be used here, but the is more for establishing vpn connectivity – Stuart Siegler Feb 07 '15 at 20:37
0

How about checking the network infos? If one has a type of VPN, I would assume there's a VPN connection.

Requires the ACCESS_NETWORK_STATE permission.

            List<NetworkInfo> connectedNetworks    = new ArrayList<>();

        if (Build.VERSION.SDK_INT >= 21)
            {
            Network[] networks = m_ConnectivityManager.getAllNetworks();

            for (Network n : networks)
                {
                NetworkInfo ni = m_ConnectivityManager.getNetworkInfo(n);

                if (ni.isConnectedOrConnecting())
                    {
                    connectedNetworks.add(ni);
                    }
                }
            }
        else
            {
            NetworkInfo[] nis = m_ConnectivityManager.getAllNetworkInfo();

            for (NetworkInfo ni : nis)
                {
                if (ni.isConnectedOrConnecting())
                    {
                    connectedNetworks.add(ni);
                    }
                }
            }

        boolean bHasVPN = false;

        if (Build.VERSION.SDK_INT >= 21)
            {
            for (NetworkInfo ni : connectedNetworks)
                {
                bHasVPN |= (ni.getType() == ConnectivityManager.TYPE_VPN);
                }
            }
chksr
  • 192
  • 3
  • 13
0

This works for API < 21 too :

ConnectivityManager manager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
        NetworkInfo[] infos=manager.getAllNetworkInfo();
        boolean VPNConnected = false;
        for (NetworkInfo info : infos){
            if ("VPN".equalsIgnoreCase(info.getTypeName()){
                if (info.getState() == NetworkInfo.State.CONNECTED){
                    VPNConnected = true;
                    break;
                }
            }
        }
mostafa3dmax
  • 997
  • 7
  • 18