7

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.

Community
  • 1
  • 1
Jon G
  • 1,656
  • 3
  • 16
  • 42
  • Tough question. If you bind the network to your application process before building and starting your process, could you be so lucky that the network is inherited? – totoro Apr 13 '16 at 15:46
  • Unfortunately, no it doesn't seem to. – Jon G Apr 14 '16 at 13:12
  • @JonG how did you end up solving this? Did you use a native implementation as suggested? I am having the exact same problem, I am able to bind "normal" network requests for API >= 21 but couldn't find a way to bind a process (I also need to use the 'ping' for testing) – soey Sep 12 '17 at 13:39
  • @soey Did you guys figure out a solution ? I tried setting default network in Java, it didn't work. Next I created Socket in Java with correct network and passed the descriptor to Native child process, it didn't work as well. – Himanshu Mar 14 '18 at 08:32
  • @Himanshu sorry, i didn't find a solution. i think it is a very specific problem. I ended up ignoring it, e.g. not executing the process in that case, which was was good enough for my use case. I think I saw a solution somewhere, where they used the native socket implementation (in C), but I cannot find it atm. Also I am not very experienced with using native code. – soey Mar 14 '18 at 13:47
  • Thanks soey for updating. I did try the native implementation as mentioned in @Michael answer. i.e. sending the message to **fwmarkd** along with **netId** and socket **fd**. The message got sent to unix socket for fwmarkd, but couldn't receive anything from it. When not waiting for receive, the code had no effect, and traffic got transferred from cellular network. Let's see if there is any more thing to try. – Himanshu Mar 14 '18 at 14:58

1 Answers1

6

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.

Michael
  • 53,859
  • 22
  • 133
  • 139
  • I didn't know that you could parcel a network, that is interesting, but my process runs a compiled binary on the command line, so I don't think I can pass the network there. – Jon G Apr 14 '16 at 13:14