Sending traffic in android app via particular connection

2.3k views Asked by At

I am developing an android app in which I specifically need to make a HTTP connection over WiFi only. It seems there were a lot of connectivity related changes in Android L and above.

This is the piece of code I'm using:

ConnectivityManager manager = (ConnectivityManager)
ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
Network[] allNetworks = manager.getAllNetworks();

for(Network network : allNetworks) {
  NetworkInfo info = manager.getNetworkInfo(network);
  if(info.getType() == ConnectivityManager.TYPE_WIFI && info.getState() == NetworkInfo.State.CONNECTED) {
   System.out.println("FOUND WIFI NETWORK!");

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
     manager.bindProcessToNetwork(network);
   }

   app.network = network;
   break;
 }
 }

As per ConnectivityManager and Network API docs I should be able to do networkObject.openConnection to get a HttpUrlConnection bound that network. But I'm getting exception something of this type

W/System.err: java.net.SocketException: Binding socket to network 586 failed: EPERM (Operation not permitted)
W/System.err:     at android.net.Network.bindSocket(Network.java:362)
W/System.err:     at android.net.Network.bindSocket(Network.java:331)
W/System.err:     at android.net.Network$NetworkBoundSocketFactory.createSocket(Network.java:182)
W/System.err:     at com.android.okhttp.internal.http.SocketConnector.connectRawSocket(SocketConnector.java:155)
W/System.err:     at com.android.okhttp.internal.http.SocketConnector.connectCleartext(SocketConnector.java:67)
W/System.err:     at com.android.okhttp.Connection.connect(Connection.java:152)
W/System.err:     at com.android.okhttp.Connection.connectAndSetOwner(Connection.java:185)
W/System.err:     at com.android.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:128)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:437)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:114)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:245)
W/System.err:     at com.mypkg.myapp.utils.HttpRequestBackground.doInBackground(HttpRequestBackground.java:90)
W/System.err:     at com.mypkg.myapp.utils.HttpRequestBackground.doInBackground(HttpRequestBackground.java:38)
W/System.err:     at android.os.AsyncTask$2.call(AsyncTask.java:295)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
W/System.err:     at java.lang.Thread.run(Thread.java:818)
W/System.err: Caused by: android.system.ErrnoException: Binding socket to network 586 failed: EPERM (Operation not permitted)

These are the network related permissions declared in the manifest

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

I'm not quite understanding what's wrong here.

The reason to do this: Android doesn't let me use wifi connection in my app when data is enabled and the AP to which it is connected does not have internet.

1

There are 1 answers

0
Nilesh On BEST ANSWER

The problem turned out to be because of a VPN app that was running in background and it was happening even with OkHttpClient which seemed to work previously.


I found the solution using OkHttpClient. Basically there's some kind of bug when network.openConnection is used which tries probably to bind to ports < 1024 which is not possible in Linux unless you are root.

Though for some reason if I do a network.getSocketFactory() and pass it to OkHttpClient, it works as it should.

Here's the piece of code I used to test:

package com.nileshgr.networktest;

import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        NetworkRequest.Builder requestbuilder = new NetworkRequest.Builder();
        requestbuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);

        ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

        cm.requestNetwork(requestbuilder.build(), new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                System.out.println("wifi network found");
                testSocket(network);
            }
        });
    }

    private void testSocket(Network network) {

        // client one, should go via wifi
        OkHttpClient.Builder builder1 = new OkHttpClient.Builder();
        builder1.socketFactory(network.getSocketFactory());
        OkHttpClient client1 = builder1.build();
        Request request1 = new Request.Builder().url("http://text.whatisyourip.org").build();

        Callback cb = new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println("success");
                System.out.println(response.body().string());
            }
        };

        System.out.println("sending via wifi network");

        client1.newCall(request1).enqueue(cb);

        System.out.println("Sending via data network");

        // client 2 should go via data
        OkHttpClient client2 = new OkHttpClient();
        Request request2 = new Request.Builder().url("http://text.whatisyourip.org").build();
        client2.newCall(request2).enqueue(cb);
    }
}

You should see two different public IP address in adb log - one your wifi public ip address and other your data public ip address. But make sure that in the wifi network you are connected to, allow only text.whatsiyourip.org and block everything else. It's pretty trivial if you have a router with multiple SSID support and firewalling.