android and dnsmasq: making a better dnsmasq wrapper

A few posts ago, I wrote about making android’s dnsmasq more usable by turning on local hostname resolution. Since I use my android phone as a pocket server, dnsmasq plays a vital role here.
However, one thing was bad – whenever I used usb tether connection to my PC, the whole dns thingie did not work. Why?

So, a little theory. When I enable the wifi ap mode, an interface called ap0 is brought up with a static ip 192.168.43.1.
Whenever I turn on usb tethering, usb0 pops into the existence with ip 192.168.42.129.
(On other mobile phones these settings can differ)
Fine, so when we resolve our pocket server from within the usb0 network, we want to get 192.168.42.129, and when via a wireless network – 192.168.43.1
I started by adding two distinct entries in /etc/hosts

192.168.43.1 anomalia anomalia.portable git.anomalia p.anomalia
192.168.42.129 anomalia anomalia.portable git.anomalia p.anomalia

Did it work? No! Why?

So let’s start with a little RTFM:

       -y, --localise-queries
              Return answers to DNS queries from /etc/hosts which depend on the interface over which the query was received. If a name in /etc/hosts has more than one address associated  with  it,  and  at
              least  one  of  those addresses is on the same subnet as the interface to which the query was sent, then return only the address(es) on that subnet. This allows for a server  to have multiple
              addresses in /etc/hosts corresponding to each of its interfaces, and hosts will get the correct address based on which network they are attached to. Currently  this  facility  is  limited  to
              IPv4.

This is the option we need. Sadly, it cannot be specified in the config file. That sucks. dnsmasq is spawned by either some native, or java code buried deep inside the mediatek android guts, I don’t have sources of. (Fuck you, Mediatek!).
What’s more, comand line options are likely to be hardcoded somewhere there as well. So dnsmasq is always spawned with this arguments:

--no-daemon --no-poll --no-resolv --dhcp-range=192.168.42.2,192.168.42.254,1h --dhcp-range=192.168.43.2,192.168.43.254,1h

And we can’t change this. Or can we?
Take one – a dumb and simple bash wrapper:

mount -o remount,rw /dev/mtdblock11 /system
cd /system/bin/
mv dnsmasq dnsmasq.real

And instead of dnsmasq I placed a small script:

#!/system/bin/ash
/system/bin/dnsmasq.real $* -y

It worked awesomely, until I brought all the interfaces down. It left a copy of dnsmasq running hot with 100% CPU load which didn’t terminate, draining battery really fast. Why?
My wild guess was, that the android guts grabbed a PID of the wrapper script and tried to gently terminate it, but failed (since we have a subprocess spawned), leaving dnsmasq out in the wild in a strange state (it definately didn’t like all the interfaces going down). So, shell wrapper will not work. Ideas?
Bet number two was to trap any signals in the bash wrapper and run ‘killall dnsmasq’ in the handler. Easier said than done. Again a fail. Looks like android’s shell just crashed on any attempt to use ‘trap’ in scripts.
Finally, I decided that it was time to bring in some heavy artillery and do some cross-compilation. If you didn’t sleep at the university lectures about unix, you remember that execvp/execl and friends spawn another process by replacing the application image with a new one and… preserving the PID! Just what I need. So I quickly threw up a very small and dumb wrapper in C

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
const char* app = "/system/bin/dnsmasq.real";
#define NUM_EXTRA 1
const char* extraargs[] = {
"-y"
};
 
 
void dump_argv(char** argv) {
	int i=0;
	do {
		printf("%d: %s\n",i,argv[i]);
		i++;
	} while (argv[i]!=0);
}
 
int main(int argc, char* argv[]) {
	int i,k;
	char ** narg = malloc(sizeof(char*)* (NUM_EXTRA + argc + 1));
	printf("Quick and dirty dnsmasq wrapper by Necromant (c) 2013\n");
	narg[0]=app;
	for (i=1;i<argc;i++) {
		narg[i]=argv[i];
	}
	k=NUM_EXTRA;
	for (k=0;k<NUM_EXTRA;k++) {
		narg[i++]=extraargs[k];
	}
	narg[i]=0;
	dump_argv(narg);
	execv(app,narg);
 
}

cross-compiled it

arm-none-linux-gnueabi-gcc -static -o dnsmasq wrapper.c

And placed in /system/bin where it should be. And it worked awesomely, leaving no traces behind, as if being a native solution. The only drawback – a statically compiled dnsmasq wrapper is about 600k, but it does what it is supposed to do, so I can still call it an epic win.

EDIT
Now, as suggested by Pete in the comments there’s a simpler way:

#!/system/xbin/ash
exec /system/bin/dnsmasq.real $* -y

Exec takes care of the rest. Just make sure you have the correct path to ash.

3 thoughts on “android and dnsmasq: making a better dnsmasq wrapper”

  1. I think you can eliminate the need to write the wrapper by using the exec command in your script:


    #!/system/bin/ash
    exec /system/bin/dnsmasq.real $* -y

  2. Yes, thanks, it worked out. Don’t quite remember why I didn’t think of that earlier and resorted to writing a wrapper

Leave a Reply