Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android runtime exec with NetworkRequest

I am running a command line argument in my Android application like:

ProcessBuilder pb = new ProcessBuilder(cmds);
Process process = pb.start();
process.waitFor();

Where cmds are a list of arguments to run. My commands probe a remote URL over a http connection. My device is connected to a WiFi network that does not have access to the internet, but does host the URL I want to probe. My device also has a cellular connection that does have access to the internet, but not the URL. My device is running Android 6.0 Marshmallow.

Normally in Lollipop or above, Android defaults to the network with a connection to the internet. To access WiFi networks without internet you need to use NetworkRequest, e.g: https://stackoverflow.com/a/27958106/1847734.

How can I pass an obtained Network to the above Process, so that the connection goes over my WiFi network, not my cellular network?

Do I instead need to use ConnectivityManager#bindProcessToNetwork? How do I join the process to set the network using this method? There doesn't seem to be an option to give the process.

like image 589
Jon G Avatar asked Dec 02 '15 13:12

Jon G


1 Answers

Starting from Lollipop Network is Parcelable so you can write it to a byte array and then read back. Let's start from the writing part.

final Parcel parcel = Parcel.obtain();
try {
  // Create a byte array from Network.
  parcel.writeParcelable(network, 0);
  final byte[] data = parcel.marshall();

  // Start a process.
  ProcessBuilder pb = new ProcessBuilder(cmds);
  Process process = pb.start();

  // Send serialized Network to the process.
  final DataOutputStream out = new DataOutputStream(process.getOutputStream());
  out.write(data.length);
  out.write(data);

  // Wait until the process terminates.
  process.waitFor();
} finally {
  parcel.recycle();
}

And the reading part.

// Read data from the input stream.
final DataInputStream in = new DataInputStream(System.in);
final int length = in.readInt();
final byte[] data = new byte[length];
in.readFully(data);

final Parcel parcel = Parcel.obtain();
try {
  // Restore Network from a byte array.
  parcel.unmarshall(data, 0, data.length);
  final Network network = parcel.readParcelable(null);

  // Use the Network object to bind the process to it.
  connectivityManager.bindProcessToNetwork(network);
} finally {
  parcel.recycle();
}

This code will work on Android 6.0 only. If you want it to work on Lollipop you should use ConnectivityManager.setProcessDefaultNetwork(Network) instead of ConnectivityManager.bindProcessToNetwork(Network). And this code is not going to work on devices before Android 5.0.

UPDATE:

For a non-Android process you can create a socket, bind it to the nework with Network.bindSocket(Socket) and pass a socket descriptor to the child process.

If the previous approach doesn't work for you, you can call NDK function android_setsocknetwork from multinetwork.h or even try and do what Android does when you bind a process to a given network. Everything you might be interested in happens in netd client. NetdClient sends a message to fwmarkd here passing a network id. Actual message sending happens here. But I would recommend to use this approach as the last chance way to solve your problem.

like image 110
Michael Avatar answered Oct 19 '22 12:10

Michael