Pull to refresh

OpenVPN на Android: прозрачное переключение между WiFi и «Мобильными данными» без разрыва соединений

Reading time4 min
Views35K
Здравствуйте, разрешите поделиться своим опытом.

Есть приложения критичные к разрывам связи, переподключение происходит мучительно и вообще не всегда. Поставил перед собой цель, сделать прыжки маршрутов и физических подключений прозрачными, что бы связь была постоянной и TCP коннект не рвался.


И поможет в этом старый, добрый и ламповый «openvpn». Но установка и настройка — тема давно избитая, трогать её, здесь не планируется.

Что нам нужно:

  • Сервер Linux с постоянным IP (vps, vds, dedic или просто домашний)
  • Openvpn на сервере и клиенте, настроенные на работу по UDP.
  • iproute2 — собственно для фокуса нужна работающая утилита ip на Android.
  • Так же потребуется внести изменения в исходный код openvpn-settings c последующий сборкой apk.


Приступим

Начнем с правки «openvpn-settings». Суть в том, что при любом изменении в сетевой конфигурации нативному демону openvpn посылается команда SIGUSR1, что заставляет его отключаться и подключаться по новой. Для наших целей это вред, но нам все равно желательно иметь возможность выполнять некоторые действия при изменении сетевой конфигурации.

Кто-то скажет, но ведь есть persist-tun. Отвечаю, работает не так, как хочется, openvpn дергает скрипты «лежать-вставать» каждый раз, когда приходит SIGUSR1. И в принципе отличить по имеющимся переменным перезапуск от старта, без выкрутасов почти не возможно. Это подрывает всю затею, а хочется просто и надежно.

Поэтому мы заменим отправку SIGUSR1 на вызов shell скрипта.

Скачиваем исходники командой
hg clone code.google.com/p/android-openvpn-settings
Открываем файл
\src\de\schaeuffelhut\android\openvpn\service\ManagementThread.java
Находим функцию public void sendSignal(int s) её мы и будем редактировать.
Комментируем отправку SIGUSR1 и вставляем вызов нашего скрипта с правами рут.
Файл скрипта у нас будет называться точно так же как и файл подключения, но дополненный расширением sh.

Должно получится так:
case SIGUSR1:
			//sendCommand( new SimpleCommand( "signal SIGUSR1" ) );
         new Shell(
				"OpenVPN-Settings-ip-route",
				 String.format(
                                   "/system/bin/sh %s.sh",
                                   Util.shellEscape(mDaemonMonitor.mConfigFile.getAbsolutePath())
                                 ),
				Shell.SU
		).run();
break;


Всё, можно собирать, подписывать, устанавливать. Перед установкой обязательно деинсталлируйте старый «openvpn-setting» из системы.

Далее проверим конфигурации подключения openvpn.

Сервер

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

up "/bin/sh /etc/openvpn/androidupdown"
down "/bin/sh /etc/openvpn/androidupdown"

Скрипт должен быть примерно таким:
NATGW="1.2.3.4"          # ip с которого будет натится клиент
NATDEV="eth0"               # интерфейс с которого клиент получает доступ в общую сеть
NATNET="10.9.8.0/24" # vpn подсеть, которую надо транслировать во внешний мир

sysctl -w net.ipv4.ip_forward=1

ACT="A"
[ "down" = "$script_type" ] && ACT="D"

iptables -t nat -$ACT POSTROUTING -s $NATNET -o $NATDEV -j SNAT --to-source $NATGW



Клиент

Во первых надо убрать директиву redirect-gateway, если она есть. Но задать up и down с указанием скрипта, который выполнит настройку маршрутов. Если вы помните, мы условились, что имя файла будет повторять имя конфигурации с добавление sh. Да, мы будем вызывать один и тот же скрипт и из демона, и из монитора.

Если конфигурация называется «testconfig.ovpn», то пусть будет так:

up "/system/bin/sh /sdcard/openvpn/testconfig.ovpn.sh"
down "/system/bin/sh /sdcard/openvpn/testconfig.ovpn.sh"

Cкрипт

# Общая часть
export ANDROID_PROPERTY_WORKSPACE=12,66560   # Может зависеть от устройства
export PATH=/system/bin:/system/xbin:$PATH

setprop net.dns1 8.8.8.8     # всем известный DNS
setprop net.dns2 8.8.4.4

# Сработает только при вызове демоном openvpn.
if [ "init" = "$script_context" ]; then 
  ACT="add"
  [ "down" = "$script_type" ] && ACT="del"

  ip route $ACT throw $remote_1 table 100
  ip route $ACT default dev $dev table 100

  ip rule $ACT table 100 pref 1000

fi



Здесь стоит немного пояснить.
Мы создаем альтернативную таблицу маршрутизации под номером 100 в которой указываем шлюзом по умолчанию vpn туннель. И добавляем правило маршрутизации, которое заворачивает весь трафик в нашу таблицу под номером 100, раньше чем он попадет в основную таблицу под именем main.

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

Не забывайте скрипты должны заканчиваться переводом строки.

Для разрешения запуска скриптов на Android, необходимо установить «Built-in + script» в «Preferences» которые вызываются по долгому тапу на подключении. Теперь можно пробовать.

В результате тестирования оказалось, что некоторые программы сами проверяют состояние сети и пытаются переподключаться во время любых изменений. Тут либо просить авторов исправить ситуацию. Либо самостоятельно де компилировать и исправлять. В любом случае закрытие TCP сессии происходит, не по таймауту, а так как положено.

Буду рад любой критике и замечаниям. Желаю стабильного коннекта!
Tags:
Hubs:
Total votes 26: ↑22 and ↓4+18
Comments5

Articles