Files
obsidian-vault/family/how-to/rasputin-router.md
T

852 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Rasputin OpenWrt router
created: '2026-07-22'
updated: '2026-09-17'
tags:
- openwrt
- router
- rasputin
- networking
- vless
- cloudflared
---
# Rasputin R5S OpenWrt router
> Status as of 2026-09-17: Rasputin is the OpenWrt edge/LAN router, routing normal LAN traffic through a VLESS/Xray tunnel via `tun0`, with explicit direct-WAN exceptions for upstream LAN access and Mac `cloudflared` QUIC traffic. ⚠️ **WAN переехал на `eth0` (`192.168.2.157/24`, gw `192.168.2.2`)** — см. раздел «WAN-интерфейс изменился» ниже. Транзит в GPON-сегмент `192.168.0.0/24` **сломан** — см. одноимённый раздел.
## Identity
| Field | Value |
|---|---|
| Hostname | `Rasputin` |
| Hardware | FriendlyElec NanoPi R5S |
| Board | `friendlyarm,nanopi-r5s` |
| CPU | ARMv8 Processor rev 0 |
| OpenWrt | `24.10.2`, revision `r28739-d9340319c6` |
| Target | `rockchip/armv8` |
| Kernel | `6.6.93` |
| Rootfs | squashfs |
| LAN IP | `192.168.6.1/24` |
| IPv6 ULA prefix | `fd3b:153e:9be9::/48` |
## Network Topology
```text
LAN clients
-> br-lan / 192.168.6.0/24
-> policy routing
-> most LAN traffic: table vpn -> tun0 -> badvpn-tun2socks -> Xray SOCKS -> VLESS
-> selected exceptions: table main -> WAN device
```
WAN devices are `eth0` and `usb0` in the `wan` firewall zone. **As of 2026-09-17 `eth0` is the active WAN** (it carries the default route); `usb0` is idle. At the 2026-07-22 capture this was inverted — `eth0` had no carrier and `usb0` held the default route.
Default route at the 2026-07-22 capture (historical, no longer active):
```text
default via 10.252.240.133 dev usb0 src 10.252.240.230
```
Current default route (2026-09-17):
```text
default via 192.168.2.2 dev eth0 proto static src 192.168.2.157
```
That is, Rasputin gets `192.168.2.157/24` from the upstream router `192.168.2.2` and reaches the internet through it; `192.168.2.2` in turn sits behind GPON `192.168.0.1`.
VPN table:
```text
default dev tun0 scope link
```
## Interfaces
### LAN
`br-lan` bridges:
```text
eth1
eth2
phy0-ap0
```
LAN config:
```text
network.lan=interface
network.lan.device='br-lan'
network.lan.proto='static'
network.lan.ipaddr='192.168.6.1'
network.lan.netmask='255.255.255.0'
network.lan.ip6assign='60'
network.lan.dns='8.8.8.8'
```
### WAN
`eth0`:
```text
network.wan=interface
network.wan.device='eth0'
network.wan.proto='dhcp'
network.wan.peerdns='0'
network.wan.type='bridge'
```
`usb0`:
```text
network.usbwan=interface
network.usbwan.proto='dhcp'
network.usbwan.device='usb0'
```
Firewall `wan` zone includes:
```text
wan
wan6
wwan
usbwan
```
### VPN
`tun0` is declared as an unmanaged interface:
```text
network.vpn=interface
network.vpn.proto='none'
network.vpn.device='tun0'
```
`tun0` is created/used by `badvpn-tun2socks`, launched from `/etc/init.d/tunvpn`.
## DHCP And Static Leases
DHCP server:
```text
dhcp.lan=dhcp
dhcp.lan.interface='lan'
dhcp.lan.start='100'
dhcp.lan.limit='150'
dhcp.lan.leasetime='12h'
dhcp.lan.dhcp_option='6,1.1.1.1,1.0.0.1'
dhcp.lan.dhcpv4='server'
dhcp.lan.dhcpv6='server'
dhcp.lan.ra='server'
dhcp.lan.ra_slaac='1'
dhcp.lan.ra_flags='managed-config' 'other-config'
```
Static DHCP reservations added 2026-07-14 for the Mac:
```text
dhcp.@host[0].name='mbp-black-eth'
dhcp.@host[0].mac='00:e0:4c:68:04:88'
dhcp.@host[0].ip='192.168.6.104'
dhcp.@host[1].name='mbp-black-wifi'
dhcp.@host[1].mac='66:02:b1:8b:2f:94'
dhcp.@host[1].ip='192.168.6.173'
```
Backup made before this change:
```text
/etc/config/dhcp.bak.20260714-195408
```
## VLESS / Xray Stack
Packages present include:
```text
badvpn
xray-core
v2ray-core
v2ray-extra
v2ray-geoip
v2ray-geosite
v2raya
luci-app-v2raya
sing-box
nftables-json
iptables-nft
```
Runtime processes:
```text
badvpn-tun2socks --tundev tun0 --netif-ipaddr 10.99.0.2 --netif-netmask 255.255.255.252 --socks-server-addr 127.0.0.1:1080 --loglevel none
/usr/bin/xray run -confdir /etc/xray -config /etc/xray/config.json -format json
```
`v2raya` is installed, but the observed UCI service config previously had `enabled='0'`. The active traffic path is the custom `tunvpn` service plus direct Xray config.
### Xray Config
File:
```text
/etc/xray/config.json
```
Structure:
```json
{
"log": {
"loglevel": "debug",
"access": "/tmp/xray-access.log",
"error": "/tmp/xray-error.log"
},
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 1080,
"protocol": "socks"
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "188.239.191.235",
"port": 443,
"users": [
{
"id": "<redacted-vless-user-id>",
"flow": "xtls-rprx-vision",
"encryption": "none"
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"serverName": "node3.sysnx.net",
"publicKey": "<redacted-reality-public-key>",
"shortId": "<redacted-reality-short-id>",
"fingerprint": "qq"
}
}
}
],
"routing": {
"rules": [
{ "type": "field", "outboundTag": "proxy", "network": "tcp,udp" }
]
}
}
```
Secrets are intentionally redacted in this note. The live file on the router contains the actual VLESS UUID and REALITY parameters.
## `tunvpn` Service
File:
```text
/etc/init.d/tunvpn
```
Purpose:
- Start `badvpn-tun2socks`.
- Populate routing table `vpn` with default route through `tun0`.
- Add policy rules so LAN traffic uses the VPN table.
- Add explicit Cloudflare Tunnel QUIC bypass rules for the Mac.
Current script:
```sh
#!/bin/sh /etc/rc.common
START=95
STOP=10
start() {
badvpn-tun2socks --tundev tun0 --netif-ipaddr 10.99.0.2 --netif-netmask 255.255.255.252 --socks-server-addr 127.0.0.1:1080 --loglevel none &
sleep 2
ip route replace default dev tun0 table vpn
# cloudflared QUIC direct bypass for Mac Ethernet/Wi-Fi
ip rule add priority 98 from 192.168.6.173 ipproto udp dport 7844 lookup main 2>/dev/null
ip rule add priority 99 from 192.168.6.104 ipproto udp dport 7844 lookup main 2>/dev/null
ip rule add iif br-lan lookup vpn priority 101
}
stop() {
killall badvpn-tun2socks
ip rule del priority 98 2>/dev/null
ip rule del priority 99 2>/dev/null
ip rule del iif br-lan lookup vpn 2>/dev/null
ip route flush table vpn
}
```
Backup made before the Cloudflare direct-bypass update:
```text
/etc/init.d/tunvpn.bak.20260714-195530
```
## Policy Routing
Current rules:
```text
0: from all lookup local
98: from 192.168.6.173 ipproto udp dport 7844 lookup main
99: from 192.168.6.104 ipproto udp dport 7844 lookup main
100: from all to 192.168.2.0/24 lookup main
101: from all iif br-lan lookup vpn
32766: from all lookup main
32767: from all lookup default
```
Meaning:
- Mac Wi-Fi `192.168.6.173` UDP/7844 goes direct through `main`.
- Mac Ethernet `192.168.6.104` UDP/7844 goes direct through `main`.
- Traffic to upstream LAN `192.168.2.0/24` goes direct through `main`.
- 🔴 Traffic to the GPON segment `192.168.0.0/24` is **NOT** covered by rule `100` (2026-09-17) → falls through to rule `101``table vpn``tun0`. Доступ к GPON-сегменту из LAN `192.168.6.0/24` не работает. See the «🔴 Транзит Rasputin → 192.168.0.0/24» section.
- All other traffic entering from LAN bridge `br-lan` uses `table vpn`.
- `table vpn` sends default traffic through `tun0`.
`/etc/rc.local` adds the upstream-LAN bypass:
```sh
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.
ip rule add to 192.168.2.0/24 lookup main priority 100
exit 0
```
⚠️ Здесь только `192.168.2.0/24`. Строки для `192.168.0.0/24` нет — это и есть корневая причина сломанного транзита в GPON-сегмент. После ребута текущий `ip rule 100` пересоздаётся именно из этого файла.
## Firewall Model
Default firewall policy:
```text
input: REJECT
output: ACCEPT
forward: REJECT
```
Zones:
### `lan`
```text
name='lan'
input='ACCEPT'
output='ACCEPT'
forward='REJECT'
masq='1'
network='lan'
```
### `wan`
```text
name='wan'
input='REJECT'
output='ACCEPT'
forward='REJECT'
masq='1'
mtu_fix='1'
network='wan' 'wan6' 'wwan' 'usbwan'
```
### `vpn`
```text
name='vpn'
input='ACCEPT'
output='ACCEPT'
forward='ACCEPT'
masq='1'
network='vpn'
```
Forwarding:
```text
lan -> vpn
```
There is no general `lan -> wan` forwarding. Instead, selected `lan -> wan` traffic is allowed before a LAN-to-WAN killswitch.
## Important Firewall Rules
### Cloudflare Tunnel QUIC Direct Bypass
Added 2026-07-14:
```text
firewall.@rule[8].name='Allow-Mac-cloudflared-QUIC-direct'
firewall.@rule[8].src='lan'
firewall.@rule[8].dest='wan'
firewall.@rule[8].src_ip='192.168.6.104' '192.168.6.173'
firewall.@rule[8].proto='udp'
firewall.@rule[8].dest_port='7844'
firewall.@rule[8].target='ACCEPT'
```
The nft realization appears in `forward_lan` before the killswitch:
```text
ip saddr { 192.168.6.104, 192.168.6.173 } udp dport 7844 jump accept_to_wan comment "!fw4: Allow-Mac-cloudflared-QUIC-direct"
```
At capture time, this rule had matched traffic:
```text
counter packets 19 bytes 27636
```
This confirms that Mac `cloudflared` QUIC traffic was hitting the direct-WAN exception.
### Upstream LAN Bypass (`Allow-upstream-LAN`)
```text
firewall.@rule[10].name='Allow-upstream-LAN'
firewall.@rule[10].src='lan'
firewall.@rule[10].dest='wan'
firewall.@rule[10].target='ACCEPT'
firewall.@rule[10].dest_ip='192.168.2.0/24' '192.168.0.1'
```
⚠️ **Порядковый номер изменился (2026-09-17).** Актуальное правило — `firewall.@rule[0].name='Allow-upstream-LAN'`, и `dest_ip` расширен до GPON-сегмента. Его nft-реализация в `forward_lan`**две отдельные строки, `tcp` и `udp`, без `icmp`**:
```text
meta l4proto tcp ip daddr { 192.168.0.1, 192.168.0.50, 192.168.2.0/24 } counter packets 19 bytes 1216 jump accept_to_wan comment "!fw4: Allow-upstream-LAN"
meta l4proto udp ip daddr { 192.168.0.1, 192.168.0.50, 192.168.2.0/24 } counter packets 0 bytes 0 jump accept_to_wan comment "!fw4: Allow-upstream-LAN"
```
Практическое следствие: `forward` в GPON-сегмент разрешён, но без NAT ответ не вернётся, а ICMP вообще не проходит. Полный разбор — в разделе «🔴 Транзит Rasputin → 192.168.0.0/24» ниже.
---
## 🔴 ICMP к недоступному хосту в 192.168.2.0/24 отдаёт `Destination Port Unreachable` от САМОГО роутера
**Наблюдение 2026-09-15 (подтверждено дважды в одной сессии).** С Mac (`192.168.6.104`) при пинге выключенного хоста верхнего сегмента:
```text
$ /sbin/ping -c 3 192.168.2.176
92 bytes from rasputin.lan (192.168.6.1): Destination Port Unreachable
Vr HL TOS Len ID Flg off TTL Pro cks Src Dst
4 5 00 5400 a0a3 0 0000 3f 01 509d 192.168.6.104 192.168.2.176
--- 192.168.2.176 ping statistics ---
3 packets transmitted, 0 packets received, 100.0% packet loss
```
**Как это читать (и как НЕ читать):**
| Признак | Значение |
|---|---|
| `Request timeout for icmp_seq N` | нормальный признак «хост выключен» |
| `Destination Port Unreachable` от `rasputin.lan (192.168.6.1)` | **это роутер отбивает попытку, а не ответ хоста** |
| Полный дамп IP-заголовка (`Vr HL TOS … Src/Dst`) | ответ сгенерирован ICMP-модулем роутера, а не удалённой машиной |
| `100.0% packet loss` | хост не ответил |
```text
/usr/sbin/arp -an | grep 176
? (192.168.6.176) at (incomplete) on en8 ifscope [ethernet]
```
`(incomplete)` = ARP-запрос ушёл, ответа нет → **L2-ответа нет вообще**, хост мёртв или вне сети. Это правильный признак для вывода «машина недоступна».
> ⚠️ **ПИТФОЛЛ ДИАГНОСТИКИ:** строка `Destination Port Unreachable` от роутера легко читается как «порт закрыт, машина жива». Это НЕВЕРНО. Она одинаково выглядит и для хоста, который выключен, и для хоста, который висит в kernel lockup без сетевого стека. Опираться нужно на **`arp (incomplete)` + 100% loss**, а не на текст ICMP-ошибки.
> ⚠️ Проверять достижимость узлов `192.168.2.0/24` из `192.168.6.0/24` надёжнее всего **тем же каналом, которым потом будете работать**: SSH по ключу. `ssh -o ConnectTimeout=10 -o BatchMode=yes root@192.168.2.176 'uptime'` — если отвечает, хост жив, и всё дальнейшее снимается через него. Пинг/веб-порты могут врать (см. `Reject to LAN` ниже), SSH — нет.
> 🔴 **Проверка 2026-09-17: 5 из 5 ICMP-попыток к `192.168.2.2` отбиты роутером** (`Destination Port Unreachable` от `192.168.6.1`), при том что с самого Rasputin `192.168.2.2` пингуется **0% loss, 0.750.84 ms**. То есть эхо-запрос из LAN до шлюза не доходит вообще — отбой происходит локально. Практический вывод: **отсутствие пинга к `192.168.2.x` НЕ говорит о том, что хост/шлюз недоступен.** Единственный вывод — «ICMP из LAN до этого адреса зарезан». Никогда не строить на пинге вывод о состоянии аплинка; проверять TCP-каналом, которым будете работать.
> ⚠️ Ещё один контр-пример к пингу как индикатору (2026-09-17, `192.168.0.50`): с Мака — 100% loss **и без** `Destination Port Unreachable` (обычный timeout), а с Rasputin — 0% loss. Здесь пинг отражает реальную поломку policy routing, но **не** показывает, жив ли хост. Различить «хост мёртв» и «транзит сломан» можно одним дешёвым шагом: пингануть ту же цель **с самого Rasputin**. Роутер видит, клиент — нет ⇒ проблема в транзите, не в хосте.
**Границы прав внутри сегментов (напоминание):**
- Router transit `192.168.6.0/24 ↔ 192.168.2.0/24` — разрешён правилами выше.
- Между сегментами есть санитарный `Reject to LAN` в `forward_lan` → произвольные порты не пройдут, ICMP-эхо тоже.
- Порты HA (`8123`) и SSH (`22`) через транзит роутера **не проверять как индикатор живости** — индикатор это сам факт ответа SSH-сессии.
## 🔴 Транзит Rasputin → 192.168.0.0/24 (GPON-сегмент) НЕ РАБОТАЕТ
**Наблюдение 2026-09-17.** Симптом: с Мака (LAN `192.168.6.0/24`) трафик на `192.168.0.50` и `192.168.0.1` (80 порт) не проходит.
### Что подтверждено фактами
| Тест | Результат |
|---|---|
| `ping -c2 192.168.0.50` **с Мака** (`192.168.6.173`, en8) | 100% loss |
| `ping -c2 192.168.0.50` **с самого Rasputin** | ✅ 0% loss, 2.112.79 ms, ttl=63 |
| `ping -c2 192.168.2.2` с Мака | 100% loss + `Destination Port Unreachable` от `192.168.6.1` |
Вывод из таблицы: **L2/L3 от Rasputin до `192.168.0.50` живой** (`ttl=63` — один хоп). Ломается **только транзит из LAN `192.168.6.0/24`**.
### Три причины
**1. `ip rule 100` покрывает только `192.168.2.0/24`.**
Фактические правила на роутере (2026-09-17):
```text
0: from all lookup local
98: from 192.168.6.173 ipproto udp dport 7844 lookup main
99: from 192.168.6.104 ipproto udp dport 7844 lookup main
100: from all to 192.168.2.0/24 lookup main
101: from all iif br-lan lookup vpn
32766: from all lookup main
32767: from all lookup default
```
Для `192.168.0.50` правило `100` **не матчится** → пакет попадает в `101``table vpn``tun0` (VLESS). Трафик уходит в туннель вместо WAN.
**2. NAT/masq для зоны `wan` не покрывает `192.168.0.0/24`.**
Правило firewall уже расширено до GPON-сегмента:
```text
meta l4proto tcp ip daddr { 192.168.0.1, 192.168.0.50, 192.168.2.0/24 } counter packets 19 bytes 1216 jump accept_to_wan comment "!fw4: Allow-upstream-LAN"
meta l4proto udp ip daddr { 192.168.0.1, 192.168.0.50, 192.168.2.0/24 } counter packets 0 bytes 0 jump accept_to_wan comment "!fw4: Allow-upstream-LAN"
```
То есть forward разрешён, но пакет уходит с source `192.168.6.x`, а GPON-сегмент не знает обратного маршрута в `192.168.6.0/24`. Ответ вернуться не может. В `forward_lan` **нет NAT-правила** для этого направления.
**3. ICMP зарезан отдельно.**
`Allow-upstream-LAN` в nft создан в двух вариантах — `tcp` и `udp`, **без `icmp`** — и стоит выше KillSwitch. Остальной ICMP из LAN падает в `KillSwitch` (`packets 71 bytes 6192`). Отсюда `Destination Port Unreachable` от самого роутера (см. раздел про ICMP ниже).
> ⚠️ Из-за п.3 **пинг не является индикатором** доступности `192.168.0.0/24` из LAN. Проверять только тем каналом, которым будете работать (SSH/TCP).
### WAN-интерфейс изменился (факт 2026-09-17)
Ранее (capture 2026-07-22) WAN был `usb0` с адресом `10.252.240.230`. Сейчас **WAN = `eth0`**:
```text
inet 192.168.2.157/24 scope global eth0
default via 192.168.2.2 dev eth0 proto static src 192.168.2.157
```
То есть Rasputin получает адрес `192.168.2.157` **от роутера `192.168.2.2`** и выходит в интернет через него, а `192.168.2.2` — через GPON `192.168.0.1`. Топология запроса подтверждена: `rasputin → 192.168.2.2 → 192.168.0.1 (GPON) → 192.168.0.50`.
### ✅ ЧТО СДЕЛАНО (2026-09-17) — правило `ip rule` добавлено и работает
Применено и **проверено Alex руками** (80 порт на `192.168.0.50` открылся):
```sh
# на живом роутере
ip rule add to 192.168.0.0/24 lookup main priority 100
# и то же записано в /etc/rc.local (чтобы выжило ребут)
```
Бэкап до правки: `/etc/rc.local.bak.20260917-132639`
Правила после правки (две строки priority 100 — это нормально):
```text
100: from all to 192.168.2.0/24 lookup main
100: from all to 192.168.0.0/24 lookup main
101: from all iif br-lan lookup vpn
```
> 🔴 **Правка файлов на роутере — через скачивание → локальную правку → заливку.**
> `sed -i` по живому конфигу **запрещён** (и был заблокирован в этой сессии).
> Порядок: `scp` вниз → `patch` локально → `scp` наверх → `cat` для проверки.
### ⚠️ Что ОСТАЛОСЬ не сделано (осознанно, по объёму команды «ip rule»)
Alex дал команду только на `ip rule`. Следующие два пункта **не трогались**:
1. **NAT/masq для `192.168.0.0/24`** — нет. Пакет уходит с source `192.168.6.x`.
Проверка показала, что 80 порт всё равно отвечает (GPON, судя по всему, маршрутизирует
обратно), поэтому это **не блокер**, но и не гарантия.
2. **ICMP**`Allow-upstream-LAN` в nft по-прежнему только `tcp`+`udp`, без `icmp`.
Значит **пинг из LAN в `192.168.0.0/24` не работает до сих пор**, хотя TCP/UDP проходит.
Не путать «не пингуется» с «не работает».
### План ремонта (исходный, для истории)
```sh
# 1. ip rule — расширить bypass на GPON-сегмент ← СДЕЛАНО
ip rule add to 192.168.0.0/24 lookup main priority 100
# 2. та же строка в /etc/rc.local ← СДЕЛАНО
# 3. firewall: Allow-upstream-LAN dest_ip += 192.168.0.0/24, добавить proto icmp ← НЕ сделано
# 4. проверить, что masq зоны wan покрывает 192.168.0.0/24 ← НЕ сделано
```
Порядок работ по боевому конфигу: коммит ДО → правка → заливка → проверка Алекса руками → коммит ПОСЛЕ.
## Downstream LAN Access
```text
firewall.@rule[11].name='Allow-downstream-LAN'
firewall.@rule[11].src='wan'
firewall.@rule[11].dest='lan'
firewall.@rule[11].target='ACCEPT'
firewall.@rule[11].src_ip='192.168.2.0/24' '192.168.0.1'
```
### KillSwitch
```text
firewall.@rule[12].name='KillSwitch'
firewall.@rule[12].src='lan'
firewall.@rule[12].dest='wan'
firewall.@rule[12].proto='all'
firewall.@rule[12].target='REJECT'
```
In nft `forward_lan`, ordering is important:
```text
1. Allow-Mac-cloudflared-QUIC-direct
2. Allow-upstream-LAN
3. KillSwitch
4. Accept lan to vpn forwarding
5. Accept DNAT port forwards
6. Reject to LAN
```
This ordering means only explicit `lan -> wan` exceptions escape; everything else either goes through VPN routing or is rejected from WAN.
Backup made before the Cloudflare firewall change:
```text
/etc/config/firewall.bak.20260714-195530
```
## Port Forwarding
There is a port-forward named `LocalSend-to-mbp-black-eth`:
```text
firewall.@redirect[0].name='LocalSend-to-mbp-black-eth'
firewall.@redirect[0].src='wan'
firewall.@redirect[0].dest='lan'
firewall.@redirect[0].src_ip='192.168.2.0/24'
firewall.@redirect[0].proto='tcp udp'
firewall.@redirect[0].src_dport='53317'
firewall.@redirect[0].dest_ip='192.168.6.104'
firewall.@redirect[0].dest_port='53317'
firewall.@redirect[0].target='DNAT'
```
Reflection rules exist for LAN clients hitting the router WAN IP on port `53317`, redirecting to `192.168.6.104:53317`.
## Cloudflare Tunnel / Mac Bypass Context
Problem addressed:
- Mac runs `cloudflared` for `zulip.qentra.top`.
- Router routes normal LAN traffic through VLESS via `tun0`.
- Cloudflare Tunnel connector traffic should go direct to WAN rather than through VLESS.
Chosen solution:
1. Mac `cloudflared` should use QUIC, not HTTP/2.
2. Cloudflare Tunnel QUIC is UDP destination port `7844`.
3. Router sends only Mac UDP/7844 through `main` table.
4. Firewall allows only Mac UDP/7844 from LAN to WAN before the KillSwitch.
Mac-side LaunchDaemon observed earlier:
```text
/Library/LaunchDaemons/com.cloudflare.cloudflared.plist
```
Original args observed before the change:
```text
cloudflared tunnel --protocol http2 run --token <redacted-cloudflare-tunnel-token>
```
Target Mac-side args:
```text
cloudflared tunnel --protocol quic run --token <redacted-cloudflare-tunnel-token>
```
Manual Mac commands provided for that change:
```sh
sudo cp /Library/LaunchDaemons/com.cloudflare.cloudflared.plist \
/Library/LaunchDaemons/com.cloudflare.cloudflared.plist.bak.$(date +%Y%m%d-%H%M%S)
sudo /usr/libexec/PlistBuddy \
-c "Set :ProgramArguments:3 quic" \
/Library/LaunchDaemons/com.cloudflare.cloudflared.plist
sudo launchctl bootout system /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
```
Verify on Mac:
```sh
plutil -p /Library/LaunchDaemons/com.cloudflare.cloudflared.plist
tail -50 /Library/Logs/com.cloudflare.cloudflared.err.log
```
Expected log clue:
```text
protocol=quic
```
Router-side counters can verify traffic is hitting the bypass:
```sh
nft list chain inet fw4 forward_lan
```
Look for increasing counters on:
```text
Allow-Mac-cloudflared-QUIC-direct
```
## Current Effective Traffic Behavior
```text
Mac 192.168.6.104 UDP/7844 -> main routing table -> WAN direct
Mac 192.168.6.173 UDP/7844 -> main routing table -> WAN direct
Any LAN client -> 192.168.2.0/24 -> main routing table -> WAN/upstream LAN
Other LAN traffic -> vpn routing table -> tun0 -> Xray/VLESS
LAN -> WAN not explicitly allowed -> rejected by KillSwitch
```
## Operational Commands
Restart VPN tunnel policy:
```sh
/etc/init.d/tunvpn restart
```
Restart firewall:
```sh
/etc/init.d/firewall restart
```
Show policy routing:
```sh
ip rule show
ip route show table vpn
ip route show table all
```
Show Cloudflare direct firewall counter:
```sh
nft list chain inet fw4 forward_lan
```
Show Xray logs:
```sh
tail -100 /tmp/xray-access.log
tail -100 /tmp/xray-error.log
```
Show active proxy processes:
```sh
ps w | grep -Ei 'xray|badvpn|tun2socks' | grep -v grep
```
Show current DHCP static leases:
```sh
uci show dhcp | grep -E 'mbp-black|192.168.6.104|192.168.6.173|00:e0:4c:68:04:88|66:02:b1:8b:2f:94'
```
## Recovery / Rollback
DHCP backup from Mac static lease change:
```text
/etc/config/dhcp.bak.20260714-195408
```
Routing service backup from Cloudflare direct-bypass change:
```text
/etc/init.d/tunvpn.bak.20260714-195530
```
Firewall backup from Cloudflare direct-bypass change:
```text
/etc/config/firewall.bak.20260714-195530
```
Роутинг через туннель (`tunvpn`) — 2026-07-14.
Firewall/Cloudflare bypass — 2026-07-14.
GPON-транзит (`ip rule` на `192.168.0.0/24`) — 2026-09-17.
Rollback example:
```sh
cp /etc/init.d/tunvpn.bak.20260714-195530 /etc/init.d/tunvpn
chmod +x /etc/init.d/tunvpn
cp /etc/config/firewall.bak.20260714-195530 /etc/config/firewall
/etc/init.d/tunvpn restart
/etc/init.d/firewall restart
```
Rollback DHCP static leases:
```sh
cp /etc/config/dhcp.bak.20260714-195408 /etc/config/dhcp
uci commit dhcp
/etc/init.d/dnsmasq restart
```
Rollback GPON-транзита (2026-09-17):
```sh
# живая система
ip rule del to 192.168.0.0/24 lookup main priority 100
# постоянство
cp /etc/rc.local.bak.20260917-132639 /etc/rc.local
```
> ⚠️ Правки `/etc/rc.local` на роутере делать **только** через `scp`-вниз → `patch` локально →
> `scp`-наверх. `sed -i` по живому конфигу запрещён.
## Notes And Pitfalls
- The Cloudflare bypass depends on Mac `cloudflared` using QUIC. If the Mac falls back to HTTP/2/TCP/443, the router exception will not match.
- There are two Mac source IPs because the Mac can use Ethernet or Wi-Fi.
- Both Mac IPs are now static DHCP reservations on Rasputin.
- `ip rule` can match `ipproto udp dport 7844` on this OpenWrt build; this was tested and confirmed before making the persistent change.
- The firewall allow alone would not be sufficient, because policy routing rule `101` sends LAN ingress to `table vpn`. The direct `ip rule`s at priorities `98` and `99` must remain before priority `101`.
- Rule order in `forward_lan` matters: Cloudflare direct allow must stay above `KillSwitch`.
- `eth0` is the **active** WAN as of 2026-09-17 (`192.168.2.157/24`, gw `192.168.2.2`); `usb0` is idle. At the 2026-07-22 capture this was inverted (`eth0` down, `usb0` carrying the default route). Do not trust the old banner — re-read `ip route` before assuming which WAN is live.
- 🔴 **Транзит из LAN `192.168.6.0/24` в GPON-сегмент `192.168.0.0/24` — ЧАСТИЧНО ПОЧИНЕН (2026-09-17).** `ip rule` добавлено (живая система + `/etc/rc.local`), Alex проверил 80 порт на `192.168.0.50` — работает. Было три причины: `ip rule 100` не покрывал `192.168.0.0/24` (✅ исправлено); нет NAT/masq для этого направления (❌ не сделано, не блокер); ICMP не разрешён в `Allow-upstream-LAN` (❌ не сделано). Подробности — в разделе «Транзит Rasputin → 192.168.0.0/24».
- 🔴 **ICMP по-прежнему не индикатор доступности `192.168.0.0/24`.** `Allow-upstream-LAN` создан только для `tcp`+`udp`, поэтому эхо-запросы из LAN падают в `KillSwitch` и роутер отвечает `Destination Port Unreachable` от себя. **Пинг в GPON-сегмент не работает даже после починки `ip rule`** — TCP/UDP при этом проходит. Проверять TCP-портом (`nc -zv 192.168.0.50 80`) или SSH.
- Различай два похожих симптома: `Destination Port Unreachable` **от роутера `192.168.6.1`** = локальное отбивание ICMP/firewall-ом, а не закрытый порт на хосте. Признак живости — ответ на TCP/SSH, а не текст ICMP-ошибки.
- 🔴 **Ping from Mac to an unreachable `192.168.2.x` host returns `Destination Port Unreachable` from `rasputin.lan (192.168.6.1)`, NOT a plain timeout.** This is the router rejecting the attempt — it does **not** mean the host is up with a closed port. Use `arp (incomplete)` + `100% packet loss` as the liveness signal. Confirmed 2026-09-15 (see the dedicated section above).
- Liveness of `192.168.2.0/24` hosts is best verified with the channel you intend to work through: key-based SSH (`ssh -o BatchMode=yes root@192.168.2.176 'uptime'`). Ping and web ports can mislead; an answered SSH session cannot.
- **Дешёвый способ развести «хост мёртв» и «транзит сломан»:** пингануть цель *с самого Rasputin* (`ssh root@192.168.6.1 'ping -c2 <target>'`). Если роутер видит хост, а LAN-клиент — нет, проблема в policy routing/firewall, а не в хосте. Именно так 2026-09-17 локализовали GPON-проблему.
- Secrets are redacted here intentionally. Consult the live router files only when the actual VLESS or Cloudflare credentials are needed.