android & dnsmasq: делаем обертку

Несколькими постами ранее, я рассказвал, как заставить wifi точку доступа андройда работать вменяемо, с локальным разрешением имен. Так как мой телефон играет так же роль карманного сервера, то и dnsmasq играет тут на нем один из ключевых элементов.

Но после недели использования этого решения вскрылась проблема. Когда подключаю телефон к компьютеру через провод, dns до телефона ресолвится неверно. Почему?

Начнем с теории. Когда я врубаю wifi точку доступа, на телефоне поднимается интерфейс ap0 со статическим IP 192.168.43.1.
Когда я врубаю раздачу по USB, подымается usb0 с IP 192.168.42.129.
(на других телефонах все это может различаться)
Чего мы хотим получить? Чтобы на dns запрос пришедший с usb0 нам ответили, что у телефона IP 192.168.42.129, а по ap0, соответственно, 192.168.43.1
Начал я, разумеется с того, что в /etc/hosts внес две строки такого вида:

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

Заработало? Нет! Почему?

Как и положено, начинаем с RTFM, а именно мана на dnsmasq:

       -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.

Вот то, что нам надо. Печально, но это нельзя задать в конфиге. dnsmasq запускается либо какой-то нативной либой, либо вообще из жаба кода, и это закопано где-то в кишках дройдовского юзерспейса, на который исходников нет (Fuck you, Mediatek!).
Пошарившись по файловой системе, я понял, что аргументы dnsmasq’у ну уж точно не в конфиге, и остается смириться, что запускается всегда он с аргументами:

--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

Как это будем чинить?
Попытка номер раз — баш скрипт-обертка! Перемонтируем корень в rw и немного шаманим:

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

Теперь вместо бинаря dnsmasq кладем вот такой вот скриптец:

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

И оно заработало! Радовался я до того самого момента, как опустил оба интерфейса. dnsmasq при этом не прихлопнулся и тихо мирно начал отжирать все 100% загрузки CPU и с аппетитом жрал заряд батареи за пару-тройку часов. Fail. Почему?
Погадав на чашечке кофе и спросив совета у астрала, я пришел к выводу, что скорее всего, андройдовские кишки сохраняют себе PID запущенного процесса, а это получается PID того шелла, где мы исполнили нашу обертку. И пытается прихлопнуть ее, что у него, разумеется, не выходит. Че будем делать?
Вариант два. Ловить сигналы в скрипте-обертве, и исполнять там ‘killall dnsmasq’. Пробуем и опять фейл. ash у меня так и не отрабатывал корректно ‘trap’, падая с сегфолтом. И опять фейл.
По ходу пришло время подтягивать тяжелую артелерию в виде кросс-компилятора. Если Вы не спали на лекциях по юниксам, то Вы помните, что execvp/execl и прочие друзья при запуске процесса запускают его с тем же PID’ом, замещая образ приложения в памяти… То, что доктор прописал, потому я быстренько выдавил из себя уже вот такую вот обертку:

#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);
 
}

Кросс-компилируем

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

Закидываем под видом /system/bin/dnsmasq в нашу систему, проверяем… Победа! Никаких больше висящих процессов и быстро разряженной батареи, все работает как и должно. Пожалуй, единственный недостаток — статический бинарник весит почти 600к, но да и ладно — не критично. Все равно в памяти он не задерживается.

EDIT
Как предложил мне Pete в комментах к английской версии этого поста, можно не изобретать велосипед, а сделать так:

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

А exec сделает все, что требуется.

Добавить комментарий