fix(openvpn-phone): use OpenVPN server mode, push routes to the client
lint / yamllint + ansible-lint + syntax-check (push) Canceled after 0s

The TLS tunnel came up but no LAN traffic flowed: "OpenVPN for Android"
does not install the config-local `route` statements from a route-based
p2p setup, so nothing was directed into tun. Zero packets ever reached
tun2 on ru-vps.

Switch the instance to a real `server` (topology subnet, 10.80.0.0/29
pool) and hand the LAN routes to the client via `push "route ..."`.
Pushed routes are installed by every OpenVPN client, mobile included.
The client profile drops to a plain `client` config (pull), keeping
peer-fingerprint auth and inline cert/key.

Also: widen NAT/forward from /30 to /29 (server mode needs a pool) and
drop the now-stale /30 ufw route + MASQUERADE left by the p2p version.

Verified on ru-vps: "Initialization Sequence Completed", tun2 10.80.0.1/29,
ufw shows only the /29 forward rule, make openvpn-phone idempotent. The
regenerated ada-phone.ovpn must be re-imported on the phone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbuZrUoevfBgCpf5DCF4DG
This commit is contained in:
Dmitry
2026-09-03 09:58:17 +03:00
co-authored by Claude Sonnet 5
parent dfcee00232
commit 203751d874
+43 -20
View File
@@ -2,17 +2,19 @@
# ============================================================================ # ============================================================================
# Road-warrior OpenVPN профиль для телефона (Android). # Road-warrior OpenVPN профиль для телефона (Android).
# #
# Отдельный TLS point-to-point инстанс на ru-vps (порт 9444/tcp, tun2, # Отдельный OpenVPN server-инстанс на ru-vps (порт 9444/tcp, tun2, пул
# сеть 10.80.0.0/30), параллельный тоннелю site-to-site (tun0, homelab-openvpn) # 10.80.0.0/29), параллельный тоннелю site-to-site (tun0, homelab-openvpn)
# и профилю ноутбука (tun1, openvpn-laptop.yml). Клиент подключается один за # и профилю ноутбука (tun1, openvpn-laptop.yml).
# раз.
# #
# КРИПТО: peer-fingerprint, БЕЗ CA/PKI. # КРИПТО: peer-fingerprint, БЕЗ CA/PKI.
# На ru-vps генерятся две самоподписанные EC-пары (server.* и client.*), # На ru-vps генерятся две самоподписанные EC-пары (server.* и client.*),
# каждая сторона пинит другую по SHA256-отпечатку. Это «quick setup» # стороны пинят друг друга по SHA256-отпечатку. Прежняя схема со static
# из man-страницы OpenVPN 2.6+. Прежняя схема со static key (--secret) # key (--secret) отброшена: OpenVPN 2.7 в Android-клиенте её не запускает.
# отброшена: OpenVPN 2.7 в Android-клиенте запускать её отказывается #
# ("No tls-client or tls-server option"), а 2.8 уберёт совсем. # РЕЖИМ: полноценный `server` (не p2p), маршруты в LAN раздаются через
# `push "route ..."`. Через p2p static-key/tls «OpenVPN for Android»
# локальные `route` из конфига не подхватывал — тоннель вставал, но
# трафик в LAN не заворачивался. Через push маршруты ставит любой клиент.
# #
# КЛИЕНТ # КЛИЕНТ
# Android — «OpenVPN for Android» (Arne Schwabe) или OpenVPN Connect: # Android — «OpenVPN for Android» (Arne Schwabe) или OpenVPN Connect:
@@ -21,7 +23,7 @@
# ЧТО ДЕЛАЕТ # ЧТО ДЕЛАЕТ
# Ставит openvpn на ru-vps, генерит EC-пары (один раз), открывает # Ставит openvpn на ru-vps, генерит EC-пары (один раз), открывает
# 9444/tcp в UFW, поднимает systemd-юнит homelab-openvpn-phone, NAT'ит # 9444/tcp в UFW, поднимает systemd-юнит homelab-openvpn-phone, NAT'ит
# 10.80.0.0/30 -> LAN через tun0, кладёт готовый профиль в # 10.80.0.0/29 -> LAN через tun0, кладёт готовый профиль в
# ansible/generated/ada-phone.ovpn (mode 0600, содержит приватный ключ). # ansible/generated/ada-phone.ovpn (mode 0600, содержит приватный ключ).
# #
# ЗАПУСК # ЗАПУСК
@@ -43,9 +45,10 @@
phone_openvpn_port: 9444 phone_openvpn_port: 9444
phone_openvpn_interface: tun2 phone_openvpn_interface: tun2
phone_openvpn_site_interface: tun0 phone_openvpn_site_interface: tun0
phone_openvpn_network_cidr: 10.80.0.0/30 # server-режим: нужен пул адресов, поэтому /29, а не /30 p2p.
phone_openvpn_server_ip: 10.80.0.1 phone_openvpn_network_cidr: 10.80.0.0/29
phone_openvpn_client_ip: 10.80.0.2 phone_openvpn_server_subnet: 10.80.0.0
phone_openvpn_server_netmask: 255.255.255.248
phone_openvpn_remote: 157.22.231.198 phone_openvpn_remote: 157.22.231.198
phone_openvpn_data_ciphers: "AES-256-GCM:AES-128-GCM" phone_openvpn_data_ciphers: "AES-256-GCM:AES-128-GCM"
phone_openvpn_client_config_local: "{{ playbook_dir }}/../generated/{{ phone_profile_name }}.ovpn" phone_openvpn_client_config_local: "{{ playbook_dir }}/../generated/{{ phone_profile_name }}.ovpn"
@@ -191,6 +194,27 @@
src: "{{ phone_openvpn_network_cidr }}" src: "{{ phone_openvpn_network_cidr }}"
dest: "{{ homelab_lan_cidr }}" dest: "{{ homelab_lan_cidr }}"
# Переходная уборка: p2p-версия использовала /30. Убрать после ~2026-11.
- name: Drop the legacy /30 ufw forward rule
community.general.ufw:
route: true
rule: allow
delete: true
interface_in: "{{ phone_openvpn_interface }}"
interface_out: "{{ phone_openvpn_site_interface }}"
src: 10.80.0.0/30
dest: "{{ homelab_lan_cidr }}"
- name: Drop the legacy /30 MASQUERADE rule
ansible.builtin.iptables:
table: nat
chain: POSTROUTING
source: 10.80.0.0/30
destination: "{{ homelab_lan_cidr }}"
out_interface: "{{ phone_openvpn_site_interface }}"
jump: MASQUERADE
state: absent
- name: Render phone OpenVPN server config - name: Render phone OpenVPN server config
ansible.builtin.copy: ansible.builtin.copy:
dest: "{{ phone_openvpn_config }}" dest: "{{ phone_openvpn_config }}"
@@ -200,19 +224,22 @@
content: | content: |
dev {{ phone_openvpn_interface }} dev {{ phone_openvpn_interface }}
dev-type tun dev-type tun
ifconfig {{ phone_openvpn_server_ip }} {{ phone_openvpn_client_ip }}
proto tcp-server proto tcp-server
port {{ phone_openvpn_port }} port {{ phone_openvpn_port }}
tls-server server {{ phone_openvpn_server_subnet }} {{ phone_openvpn_server_netmask }}
topology subnet
dh none dh none
cert {{ phone_openvpn_server_cert }} cert {{ phone_openvpn_server_cert }}
key {{ phone_openvpn_server_key }} key {{ phone_openvpn_server_key }}
peer-fingerprint {{ phone_openvpn_client_fp }} peer-fingerprint {{ phone_openvpn_client_fp }}
data-ciphers {{ phone_openvpn_data_ciphers }} data-ciphers {{ phone_openvpn_data_ciphers }}
auth SHA256 auth SHA256
keepalive 10 60
{% for route in phone_openvpn_routes %}
push "route {{ route.network }} {{ route.netmask }}"
{% endfor %}
persist-key persist-key
persist-tun persist-tun
keepalive 10 60
verb 3 verb 3
notify: restart phone openvpn notify: restart phone openvpn
@@ -302,12 +329,11 @@
dest: "{{ phone_openvpn_client_config_local }}" dest: "{{ phone_openvpn_client_config_local }}"
mode: "0600" mode: "0600"
content: | content: |
client
dev tun dev tun
dev-type tun dev-type tun
proto tcp-client proto tcp-client
remote {{ phone_openvpn_remote }} {{ phone_openvpn_port }} remote {{ phone_openvpn_remote }} {{ phone_openvpn_port }}
ifconfig {{ phone_openvpn_client_ip }} {{ phone_openvpn_server_ip }}
tls-client
peer-fingerprint {{ phone_openvpn_server_fp }} peer-fingerprint {{ phone_openvpn_server_fp }}
data-ciphers {{ phone_openvpn_data_ciphers }} data-ciphers {{ phone_openvpn_data_ciphers }}
auth SHA256 auth SHA256
@@ -316,9 +342,6 @@
resolv-retry infinite resolv-retry infinite
nobind nobind
verb 3 verb 3
{% for route in phone_openvpn_routes %}
route {{ route.network }} {{ route.netmask }}
{% endfor %}
<cert> <cert>
{{ phone_openvpn_client_cert_raw.content | b64decode | trim }} {{ phone_openvpn_client_cert_raw.content | b64decode | trim }}
</cert> </cert>