Перейти к содержанию

Рекомендуемые сообщения

Опубликовано (изменено)
5 hours ago, Valerios said:

Добрый день

месяц уже Вашим творением пользуюсь, работает шикарно, спасибо огромное Вам, самое удобное и простое решение по моему.

Добрый

Спасибо!
 

5 hours ago, Valerios said:

по поводу рекламы в ют

на iOS с приложением shadowrocket у меня получилось реализовать выключение рекламы перед видео, так же работает окно в окне и фоновое воспроизведение

инструкцию случайно нашел у одного блогера

нельзя ли как-то подобным образом это на роутере сделать? Какие данные мне предоставить, которые бы дали понимание ?

в кратце попробую способ описать:

в приложении в настройке конфига подключения включается https, сертификат загружается в телефон и включается доверие, потом в конфиге загружается 2 модуля, модуль сертификата и модуль Модуль ют

Спасибо, предоставленной информации вполне достаточно.

К сожалению...
Интернет, он как слоёный пирог, или селёдка по шубой, или салат мимоза. Там несколько технологических уровней пирога, примерно 7 (специалисты спорят, сколько именно).
Любой роутер (Кинетик тоже) - это достаточно слабое устройство, примерно в 10-100 раз слабее любого смартфона или ПК. Он работает, так как находится на 2-3 уровне, где не требуется много вычислений или памяти. Это специальное устройство для ограниченного круга задач.
Фильтрация YT - это задача уровня 6-7, самый верх пирога. Это требует в разы больше ресурсов устройства.
Теоретически это можно (попробовать) сделать, но практически это не будет работать (это точно).

Нельзя перенести этот подход на роутер. Причина фундаментальная — это не фильтрация на уровне DNS/IP, а MITM с модификацией бинарных API-ответов внутри HTTPS-сессии. Роутер Keenetic не потянет ни по CPU, ни по RAM, и всё равно придётся ставить сертификат на каждое устройство (что ничем не лучше Shadowrocket на каждом устройстве отдельно).

Для пользователя оптимальная стратегия: Shadowrocket на iOS + ReVanced на Android + uBlock в браузере ПК. Каждая платформа — своё решение.

Технические подробности:

Spoiler
# YouTube Premium через роутер (как Shadowrocket) — анализ
 
> **Дата:** 2026-07-06
> **Источник:** запрос пользователя Valerios на форуме (NC-1812)
> **Статус:** Нецелесообразно
 
## Запрос
 
Пользователь нашёл способ блокировки рекламы YouTube на iOS через Shadowrocket:
- HTTPS-перехват с установкой сертификата
- Модуль блокировки рекламы + PiP + фоновое воспроизведение
- Вопрос: можно ли реализовать аналогичное на роутере Keenetic?
 
**Ссылки от пользователя:**
 
## Как работает Shadowrocket-модуль
 
Изучены оба файла — `.module` и `youtube.response.js` (~2500 строк минифицированного кода). Механизм состоит из 3 уровней:
 
### 1. MITM (Man-in-the-Middle) — ключевой элемент
 
```
[MITM]
hostname = *.googlevideo.com, www.youtube.com, s.youtube.com, youtubei.googleapis.com
```
 
Shadowrocket **расшифровывает HTTPS-трафик** к этим доменам. Для этого генерирует собственный CA-сертификат, устанавливает его в доверенные на устройстве, и подменяет SSL-сертификаты налету.
 
### 2. URL Rewrite — блокировка рекламных запросов
 
- `googlevideo.com/initplayback...&oad` → reject (рекламные pre-roll)
- `youtube.com/api/stats/ads` → reject
- `youtube.com/pagead`, `ptracking` → reject
- UDP-трафик к `googlevideo.com` и `youtubei.googleapis.com` → reject (QUIC)
 
### 3. Script — модификация protobuf-ответов API
 
JS-скрипт перехватывает ответы `youtubei.googleapis.com/youtubei/v1/{browse,next,player,search,...}` и:
 
- **Парсит бинарный Protocol Buffers** (не JSON!) ответ YouTube
- **Удаляет** `adPlacements`, `adSlots`, `pageadViewthroughconversion`
- **Инжектит** `pictureInPictureRender` с `active: true` → PiP
- **Инжектит** `backgroundPlayerRender` с `active: true` → фоновое воспроизведение
- **Удаляет** Shorts из навигации
- **Пересобирает** protobuf и отдаёт модифицированный ответ
 
## Можно ли сделать это на роутере Keenetic?
 
**Короткий ответ: технически возможно, но практически — нет. Не имеет смысла.**
 
### Причины
 
| Проблема | Детали |
|----------|--------|
| **MITM HTTPS** | Нужен SSL-прокси (mitmproxy / squid ssl-bump), который расшифровывает HTTPS на лету. Это +100-300% CPU на каждое соединение |
| **Ресурсы NC-1812** | ~880 МГц MIPS/ARM, 256 МБ RAM. SSL interception одного YouTube-потока = 30-50% CPU. Несколько устройств = роутер ляжет |
| **Protobuf-обработка** | Нужен JavaScript-рантайм (Node.js) на роутере для парсинга и модификации бинарных protobuf-ответов YouTube API. Node.js на MIPS = ~80 МБ RAM |
| **Сертификат на каждом устройстве** | Всё равно придётся ставить CA-сертификат роутера на КАЖДОЕ устройство в сети — iPhone, Android, ПК, TV |
| **Certificate Pinning** | Приложение YouTube на Android/iOS использует certificate pinning — отклонит поддельный сертификат. Работать будет только в браузере |
| **QUIC/HTTP3** | YouTube активно использует QUIC (UDP). На роутере надо блокировать весь QUIC чтобы заставить YouTube использовать TCP/HTTPS |
 
### Почему DNS-блокировка (AdGuard Home / Pi-hole) не поможет
 
- Реклама YouTube идёт с **тех же самых доменов** что и контент (`googlevideo.com`)
- На уровне DNS невозможно отличить рекламный видеопоток от контентного
- PiP и фоновое воспроизведение — это модификация API-ответов, DNS тут бессилен
 
## Что реально работает (альтернативы)
 
| Платформа | Решение | Как работает |
|-----------|---------|--------------|
| **iOS** | Shadowrocket (что у пользователя) | MITM-прокси на устройстве |
| **Android** | ReVanced | Патченый YouTube-клиент, не требует прокси |
| **ПК (браузер)** | uBlock Origin | Блокирует рекламу в веб-версии YouTube |
| **SmartTV / AppleTV** | YouTube Premium | Для них решений на уровне роутера нет вообще |

Видиться 3 варианта:
1. текущий - каждое устройство - своё решение
2. купить yt premium (Shadowrocket - 4.5$, yt - 9$/month, разница заметна, но Серёга Брин и Ларри Пейдж вроде заслужили), + там ещё есть фишки, которых нет во free
3. Найти и прислать аналогичное решение именно для роутера (не на базе ПК, а как отдельного устройства), тогда от него можно попробовать оттолкнуться. На данном этапе нормальных решений не найдено 🙁

🤷‍♂️

Изменено пользователем 0xkee
  • Ответов 107
  • Создана
  • Последний ответ

Топ авторов темы

Опубликовано (изменено)

Здравствуйте. Спасибо за приложение, очень удобно всё сделано. Единственное не могу настроить smart dns - идет только через системный. И тесты в smartdns geo-config не проходят: ✗ error · script_error. И поумолчанию и с моими настройками никак не работает.вместо системного DoH adguard ставил и от провайдера и просто 1.1.1.1 - всеравно не работает.

 

bag-report.txt

Изменено пользователем nikolay1980
Опубликовано
On 7/9/2026 at 7:32 PM, nikolay1980 said:

Здравствуйте. Спасибо за приложение, очень удобно всё сделано. Единственное не могу настроить smart dns - идет только через системный. И тесты в smartdns geo-config не проходят: ✗ error · script_error. И поумолчанию и с моими настройками никак не работает.вместо системного DoH adguard ставил и от провайдера и просто 1.1.1.1 - всеравно не работает.

 

Добрый день.

1. error - нужно обновить все пакеты
После этого dns-check.sh появится (версия smartdns-geo-conf 0.10.6+), и DNS Check заработает.
Но лучше подождите 0.10.7+, день-два, там улучшения в части dns check web ui.

2. ("идет только через системный") — в баг-репорте всё работает корректно (split active, DNS тесты проходят, маршрутизация split), так что скорее всего это тоже решится обновлением...

Опубликовано

Спасибо.

Решил прописав в системный dns кинетика 192.168.1.1:6053, теперь работает. И после обновления smartdns-geo-conf 0.10.6+ DNS Check заработал.

Опубликовано
23 minutes ago, nikolay1980 said:

Решил прописав в системный dns кинетика 192.168.1.1:6053

Это должен делать smartdns-redirect, это его единственное назначение, но можно и так
 

 

24 minutes ago, nikolay1980 said:

И после обновления smartdns-geo-conf 0.10.6+ DNS Check заработал.

👍
вот новые версии, там сильно лучше с визуализацией checks

geo-split-data_0.6.0_all.ipk geo-split_0.17.0_all.ipk keenetic-entware-extras_0.16.6_all.ipk smartdns-geo-conf_0.10.7_all.ipk smartdns-redirect_0.3.4_all.ipk webui_0.32.2_all.ipk

Опубликовано

Спасибо за отличную работу.Обнаружил проблему с Private AdGuard DoH (https://d.adguard-dns.com/dns-query/<ID>). При генерации server-https с параметром -host-ip запросы идут, но не попадают в персональный профиль AdGuard DNS. После удаления -host-ip всё начинает работать корректно и запросы отображаются в личном кабинете. Возможно, для пользовательских DoH-провайдеров не стоит принудительно задавать -host-ip.

 

Опубликовано
2 hours ago, nikolay1980 said:

Обнаружил проблему с Private AdGuard DoH (https://d.adguard-dns.com/dns-query/<ID>). При генерации server-https с параметром -host-ip запросы идут, но не попадают в персональный профиль AdGuard DNS. После удаления -host-ip всё начинает работать корректно и запросы отображаются в личном кабинете. Возможно, для пользовательских DoH-провайдеров не стоит принудительно задавать -host-ip.

Поправлено.
Теперь для DOH серверов можно не указывать IP1/2, а для private DOH - не нужно.
Тогда -host-ip не будет в итоговом конфиге.
Отражено в документации.
+ слегка улучшены ui чекеры 
Пожалуйста, подтвердите...

geo-split-data_0.6.0_all.ipk geo-split_0.17.3_all.ipk keenetic-entware-extras_0.16.7_all.ipk smartdns-geo-conf_0.10.8_all.ipk smartdns-redirect_0.3.4_all.ipk webui_0.32.5_all.ipk

Опубликовано

В этом скрипте надо поправить /opt/keenetic-entware-extras/smartdns-geo-conf/scripts/generate-conf.sh секцию _emit_doh_server, тогда в  /opt/etc/smartdns/dns-servers-other.conf они не прописываются:

server-https https://d.adguard-dns.com/dns-query/123abcd \

    -host-name d.adguard-dns.com \

    -http-host d.adguard-dns.com \

    -tls-host-verify d.adguard-dns.com \

    -interface nwg0

Сам пытался поправить, но что то не получается.

Опубликовано
6 minutes ago, nikolay1980 said:

В этом скрипте надо поправить /opt/keenetic-entware-extras/smartdns-geo-conf/scripts/generate-conf.sh секцию _emit_doh_server, тогда в  /opt/etc/smartdns/dns-servers-other.conf они не прописываются:

поправлено.... в версии smartdns-geo-conf_0.10.8_all.ipk
 

Spoiler

image.thumb.png.2f386704a3093b05e577c8be01f3d1a2.png

 

Опубликовано
2 hours ago, dimsok said:

Доброе утро!

Еще раз спасибо! Подскажите а почему intel.com определяет меня как Россия? 

Доброе утро.
Пришлите, пожалуйста, скрин и url !
Вроде не определяет как Россия, но может кудато не туда смотрю?
Можно в личку сообщением.
 

Опубликовано (изменено)
В 06.07.2026 в 22:46, 0xkee сказал:

пишите какие вопросы остались!

Ну, напрямую к geoip оставшиеся вопросы отношения не имеют. Но если сможете подсказать, буду рад: какой хук реагирует на обновление/продление ip-адреса? или как еще поправить ситуацию, когда при обновлении ip-адреса обновляется и вся информация, полученная от dhcp-сервера, в том числе и маршрут по умолчанию перезаписывается у меня с awg0 обратно на интерфейс ISP. Скрипт в /opt/etc/ndm/wan.d/ вероятно не срабатывает при обновлении/продлении аренды. Может быть /opt/etc/ndm/ifipchanged.d? Правда, в описании сказано, что сработает при изменении ip-адреса, но адрес-то остается тот же, продлевается его аренда.

 

Цитата

вебка есть отдельная от прошивки (:8080/custom/), без интеграции, но она использует стоковый rest api, а он 99% что менялся...

У меня эксперименты с неподдерживаемой уже ОС 2.16

Спойлер

Да и пусть бы без интеграции. Если интересно, происходит следующее:

~ # opkg install /opt/root/Distr/GeoIP/webui_0.33.2_all.ipk
Installing webui (0.33.2) to root...
Configuring webui.
logrotate config deployed: /opt/etc/logrotate.d/nginx-webui
 Stopping nginx WebUI dashboard... not running (no PID).
 Starting nginx WebUI dashboard... ERROR: main-*.js not found in /tmp/ew-webui
webui installed. Dashboard: http://10.5.33.1:8080/custom/
Config: /opt/keenetic-entware-extras/webui/config/nginx.conf
Status: /opt/keenetic-entware-extras/webui/scripts/status.sh

Нет nginx, нет и странички. Может я опять что-то простое упустил?

 

С intel.com у меня ситуация как и у dimsok.

Скриншот с сайтом и трассировкой через туннель и url. В конфигурации geoip сейчас только гео-зона ru.

Спойлер

url:

https://www.intel.com

260713-www.intel_com.thumb.jpg.7a6211618df68a3a735a87be88d177df.jpg

без www, просто intel.com не открывается.

 

Изменено пользователем KeenTaur
Опубликовано
1 hour ago, KeenTaur said:

Ну, напрямую к geoip оставшиеся вопросы отношения не имеют. Но если сможете подсказать, буду рад: какой хук реагирует на обновление/продление ip-адреса?

Почему не подсказать...
Иишка считает так:

 

Spoiler

Ответ для KeenTaur: хуки NDM и продление DHCP-аренды

Короткий ответ

Ни один стандартный NDM хук не гарантированно срабатывает при продлении DHCP-аренды без смены IP. Это фундаментальная проблема подхода "подменить default route в main table".

Разбор хуков

Директория Когда срабатывает Продление аренды (тот же IP)
/opt/etc/ndm/wan.d/ WAN up/down события Не срабатывает — WAN не падал
/opt/etc/ndm/ifstatechanged.d/ Смена состояния интерфейса (up/down/link) ⚠️ На некоторых прошивках NDM может кратковременно "передёрнуть" интерфейс при ребинде — тогда сработает. Не гарантировано.
/opt/etc/ndm/ifipchanged.d/ Смена IP-адреса интерфейса Не срабатывает — IP не менялся
/opt/etc/ndm/netfilter.d/ NDM перестраивает iptables К маршрутам не относится

Почему это происходит

При DHCP renew/rebind клиент получает от сервера полный набор опций, включая option 3 (Router). Keenetic NDM обрабатывает ответ и восстанавливает default route через ISP-интерфейс — это штатное поведение, NDM считает, что он "владеет" маршрутом по умолчанию.

Решения (от лучшего к худшему)

1. Приоритеты подключений через Keenetic UI (рекомендуемый)

Если нужно весь трафик пустить через awg0 — настройте приоритет подключений в веб-интерфейсе Keenetic:

  • Интернет → Приоритеты подключений (или Маршруты и правила на новых прошивках)
  • Поставьте awg0 выше ISP-подключения

Тогда NDM сам управляет default route и при DHCP renew корректно восстановит его через awg0, а не через ISP. Это единственный путь, при котором NDM не будет бороться с вами за default route.

2. Custom routing table + ip rule (надёжный обходной путь)

Не трогайте default route в main table вообще. Вместо этого:

# Создать свою таблицу с default через awg0
ip route add default dev awg0 table 100
# Подключить её с высоким приоритетом (ниже номер = выше приоритет)
ip rule add from all lookup 100 priority 100

DHCP renew перезаписывает default route только в main table (priority 32766). Ваша таблица 100 с priority 100 проверяется раньше — трафик уходит в awg0. Именно этот подход используется в geo-split (таблицы 1000/1001, priority 50/51) — DHCP не затрагивает кастомные таблицы.

Скрипт для /opt/etc/ndm/ifstatechanged.d/ (страховка на случай поднятия awg0):

#!/opt/bin/sh
[ "${1:-}" = "hook" ] || exit 0
# Проверить что правило на месте
ip rule show | grep -q "lookup 100" || {
    ip route replace default dev awg0 table 100
    ip rule add from all lookup 100 priority 100
}

3. Cron watchdog (грубый но рабочий fallback)

# /opt/etc/crontab — проверка каждую минуту
* * * * * ip route | grep "^default" | grep -q "dev awg0" || ip route replace default dev awg0

Задержка до 1 минуты, но 100% надёжность.

Рекомендация

Если цель — весь трафик через VPN: используйте вариант 1 (приоритеты через UI Keenetic). NDM будет союзником, а не противником.

Если цель — часть трафика через VPN (GeoIP / списки доменов): используйте вариант 2 (custom table + ip rule). Это именно то, что делает geo-split, и DHCP renewal его не затрагивает в принципе.

Если есть доп. вводные, дайте знать...
 

1 hour ago, KeenTaur said:

У меня эксперименты с неподдерживаемой уже ОС 2.16

Давайте сделаем проще, вот скрипт scripts/compat-check.sh, он проверяет апи на стоке 2.16 (и других). Пришлите выхлоп, пожалуйста. Поскипайте ***, если важное.
Будет понятно, насколько обратно совместимо.

Spoiler

#!/opt/bin/sh
# compat-check.sh — Keenetic firmware compatibility check for webui package.
# Run on router to verify stock API availability before installing webui.
# Covers every stock Keenetic dependency used by keenetic-entware-extras/webui.
#
# Usage: sh /opt/root/compat-check.sh
# Output: text report with ✓/✗ for each check + raw samples for analysis.
set -eu

# ── Helpers ────────────────────────────────────────────────────────────
OK=0; WARN=0; FAIL=0
pass() { OK=$((OK + 1));   printf "  ✓ %s\n" "$1"; }
warn() { WARN=$((WARN+1)); printf "  ⚠ %s\n" "$1"; }
fail() { FAIL=$((FAIL+1)); printf "  ✗ %s\n" "$1"; }
hdr()  { printf "\n━━━ %s ━━━\n" "$1"; }
sample() {
    # Print indented sample (max N lines)
    _max="${2:-10}"
    echo "$1" | head -n "$_max" | sed 's/^/    | /'
    _total=$(echo "$1" | wc -l)
    if [ "$_total" -gt "$_max" ]; then
        printf "    | ... (%s lines total)\n" "$_total"
    fi
}

# ══════════════════════════════════════════════════════════════════════
hdr "1. FIRMWARE INFO"
# ══════════════════════════════════════════════════════════════════════

# 1a. ndmc "show version" — used by patch-stock-ui.sh and status.sh
if command -v ndmc >/dev/null 2>&1; then
    pass "ndmc binary: found ($(command -v ndmc))"
    VER_OUT=$(ndmc -c "show version" 2>/dev/null) || VER_OUT=""
    if [ -n "$VER_OUT" ]; then
        pass "ndmc 'show version': responds"
        # We parse: title field
        FW_TITLE=$(echo "$VER_OUT" | awk '/title:/ { print $2; exit }')
        if [ -n "$FW_TITLE" ]; then
            pass "firmware title: $FW_TITLE"
        else
            warn "firmware title: not found in output"
        fi
        printf "  raw sample:\n"
        sample "$VER_OUT" 15
    else
        fail "ndmc 'show version': empty response"
    fi
else
    fail "ndmc binary: not found"
fi

# 1b. /tmp/run/version.js — alternative firmware info
if [ -f /tmp/run/version.js ]; then
    pass "/tmp/run/version.js exists"
    sample "$(cat /tmp/run/version.js)" 5
else
    warn "/tmp/run/version.js not found"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "2. STOCK HTDOCS (WebUI Angular SPA)"
# ══════════════════════════════════════════════════════════════════════

# NDMS 4.x/5.x: /usr/share/htdocs_, NDMS 2.x: may be /usr/share/htdocs (no underscore)
HTDOCS=""
for _htdocs_try in /usr/share/htdocs_ /usr/share/htdocs /www /tmp/www; do
    if [ -d "$_htdocs_try" ] && [ -f "$_htdocs_try/index.html" ]; then
        HTDOCS="$_htdocs_try"
        break
    fi
done

if [ -n "$HTDOCS" ]; then
    pass "htdocs dir: $HTDOCS exists"
    HTDOCS_COUNT=$(find "$HTDOCS" -maxdepth 1 -type f 2>/dev/null | wc -l)
    pass "htdocs root files: $HTDOCS_COUNT"
    printf "  listing:\n"
    sample "$(ls -la "$HTDOCS"/ 2>/dev/null)" 30
else
    fail "htdocs dir: not found (tried /usr/share/htdocs_, /usr/share/htdocs, /www, /tmp/www)"
    HTDOCS="/usr/share/htdocs_"  # fallback for subsequent checks
    # Check alternative locations
    for alt in /usr/share/htdocs /www /tmp/www; do
        if [ -d "$alt" ]; then
            warn "alternative htdocs: $alt found"
            printf "  listing:\n"
            sample "$(ls -la "$alt"/ 2>/dev/null)" 15
        fi
    done
fi

# 2a. main-*.js (Angular bundle — required by patch-stock-ui.sh)
MAIN_JS=$(find "$HTDOCS" -maxdepth 1 -name "main-*.js" 2>/dev/null | head -1)
if [ -n "$MAIN_JS" ]; then
    pass "main-*.js: found ($(basename "$MAIN_JS"))"
    JS_SIZE=$(wc -c < "$MAIN_JS")
    pass "main-*.js size: ${JS_SIZE} bytes"
    # Check for DashboardSection enum (used by patch detection)
    ENUM=$(grep -o '[A-Za-z][A-Za-z0-9]*={INTERNET:"INTERNET"' "$MAIN_JS" 2>/dev/null | head -1 | sed 's/=.*//')
    if [ -n "$ENUM" ]; then
        pass "DashboardSection enum: $ENUM"
    else
        warn "DashboardSection enum: not found (no patch set will match)"
    fi
else
    fail "main-*.js: NOT FOUND — patch-stock-ui.sh will fail"
fi

# 2b. styles-*.css (stock CSS — used for theme consistency)
STYLES_CSS=$(find "$HTDOCS" -maxdepth 1 -name "styles-*.css" 2>/dev/null | head -1)
if [ -n "$STYLES_CSS" ]; then
    pass "styles-*.css: found ($(basename "$STYLES_CSS"))"
else
    warn "styles-*.css: not found (custom dashboard will use fallback theme)"
fi

# 2c. index.html
if [ -f "$HTDOCS/index.html" ]; then
    pass "index.html: found"
    # Check if it's an Angular SPA (various Angular markers across versions)
    if grep -q "app-root\|ng-app\|beasties-container\|main-.*\.js" "$HTDOCS/index.html" 2>/dev/null; then
        pass "index.html: Angular SPA detected"
    else
        warn "index.html: no Angular markers (old UI framework?)"
    fi
    printf "  head sample:\n"
    sample "$(head -20 "$HTDOCS/index.html")" 20
else
    fail "index.html: not found in htdocs"
fi

# 2d. favicon (referenced by custom dashboard)
if [ -f "$HTDOCS/assets/images/favicon/favicon.ico" ] || [ -f "$HTDOCS/favicon.ico" ]; then
    pass "favicon: found"
else
    warn "favicon: not found (minor — custom dashboard icon will 404)"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "3. ndmc 'show interface' (interface labels)"
# ══════════════════════════════════════════════════════════════════════

# Used by: api-router.lua get_iface_labels()
# We parse: id, description, type, link, address fields
if command -v ndmc >/dev/null 2>&1; then
    IFACE_OUT=$(ndmc -c "show interface" 2>/dev/null) || IFACE_OUT=""
    if [ -n "$IFACE_OUT" ]; then
        pass "ndmc 'show interface': responds"
        IFACE_BLOCKS=$(echo "$IFACE_OUT" | grep -c '^Interface, name' 2>/dev/null || echo "0")
        pass "interface blocks: $IFACE_BLOCKS"

        # Check for key fields we parse
        for field in "id:" "description:" "type:" "link:" "address:"; do
            COUNT=$(echo "$IFACE_OUT" | grep -c "$field" 2>/dev/null || echo "0")
            if [ "$COUNT" -gt 0 ]; then
                pass "field '$field' present ($COUNT occurrences)"
            else
                warn "field '$field' NOT found"
            fi
        done

        # Show first interface block as sample
        printf "  first interface block:\n"
        FIRST_BLOCK=$(echo "$IFACE_OUT" | awk '/^Interface, name/{n++} n==1{print} n==2{exit}')
        sample "$FIRST_BLOCK" 30
    else
        fail "ndmc 'show interface': empty response"
    fi
else
    fail "ndmc not available (skipping)"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "4. ndmc 'show ip hotspot' (client list)"
# ══════════════════════════════════════════════════════════════════════

# Used by: api-router.lua system_clients(), route-check.sh --from
# We parse: host, mac, name, hostname, ip, active, id (Bridge*), description
if command -v ndmc >/dev/null 2>&1; then
    HOTSPOT_OUT=$(ndmc -c "show ip hotspot" 2>/dev/null) || HOTSPOT_OUT=""
    if [ -n "$HOTSPOT_OUT" ]; then
        pass "ndmc 'show ip hotspot': responds"
        HOST_COUNT=$(echo "$HOTSPOT_OUT" | grep -c "host:" 2>/dev/null || echo "0")
        pass "hotspot entries (host:): $HOST_COUNT"

        for field in "mac:" "name:" "ip:" "active:" "hostname:"; do
            COUNT=$(echo "$HOTSPOT_OUT" | grep -c "$field" 2>/dev/null || echo "0")
            if [ "$COUNT" -gt 0 ]; then
                pass "field '$field' present ($COUNT)"
            else
                warn "field '$field' NOT found"
            fi
        done

        printf "  first client block:\n"
        FIRST_CLIENT=$(echo "$HOTSPOT_OUT" | head -30)
        sample "$FIRST_CLIENT" 25
    else
        warn "ndmc 'show ip hotspot': empty response (maybe no clients connected)"
    fi
else
    fail "ndmc not available (skipping)"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "5. STOCK HTTPD (auth proxy + RCI)"
# ══════════════════════════════════════════════════════════════════════

# Used by: nginx.conf /auth (POST auth guard), /rci/ (stock UI proxy)
# Detect stock httpd on port 80
HTTPD_LISTEN=$(netstat -tlnp 2>/dev/null | grep ":80 " | head -3)
if [ -n "$HTTPD_LISTEN" ]; then
    pass "port 80: listening"
    sample "$HTTPD_LISTEN" 3
else
    warn "port 80: nothing listening (stock httpd may use different port)"
fi

# Try HTTP request to stock httpd
ROUTER_IP=$(ip -4 addr show br0 2>/dev/null | awk '/inet / {split($2,a,"/"); print a[1]; exit}')
if [ -z "$ROUTER_IP" ]; then
    ROUTER_IP="127.0.0.1"
fi
printf "  router IP (br0): %s\n" "$ROUTER_IP"

if command -v curl >/dev/null 2>&1; then
    # Test /auth endpoint (used for POST action authorization)
    AUTH_HEADERS=$(curl -s -D - -o /dev/null "http://${ROUTER_IP}:80/auth" 2>/dev/null) || AUTH_HEADERS=""
    AUTH_CODE=$(echo "$AUTH_HEADERS" | awk '/^HTTP\//{print $2}' | tail -1)
    if [ -n "$AUTH_CODE" ]; then
        pass "/auth response: HTTP $AUTH_CODE"
        # Detect auth type (NDMS 2.x=Digest, 4.x/5.x=session/cookie)
        AUTH_TYPE=$(echo "$AUTH_HEADERS" | grep -i "^WWW-Authenticate:" | head -1)
        if [ -n "$AUTH_TYPE" ]; then
            printf "    auth type: %s\n" "$AUTH_TYPE"
            if echo "$AUTH_TYPE" | grep -qi "Digest"; then
                pass "auth method: Digest (NDMS 2.x style)"
            elif echo "$AUTH_TYPE" | grep -qi "Basic"; then
                pass "auth method: Basic"
            else
                pass "auth method: $(echo "$AUTH_TYPE" | sed 's/WWW-Authenticate: *//')"
            fi
        else
            pass "auth method: session/cookie (no WWW-Authenticate header)"
        fi
    else
        warn "/auth: no response from stock httpd"
    fi

    # Test /rci/ endpoint (used by stock Angular SPA)
    RCI_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://${ROUTER_IP}:80/rci/" 2>/dev/null) || RCI_CODE="000"
    if [ "$RCI_CODE" != "000" ]; then
        pass "/rci/ response: HTTP $RCI_CODE"
        # Get a small RCI sample
        RCI_SAMPLE=$(curl -s "http://${ROUTER_IP}:80/rci/" 2>/dev/null | head -c 500)
        if [ -n "$RCI_SAMPLE" ]; then
            printf "  /rci/ sample (first 500 bytes):\n"
            sample "$RCI_SAMPLE" 10
        fi
    else
        warn "/rci/: no response"
    fi

    # Test authenticated RCI call (show version via RCI, auth with web_password)
    # NDMS 2.x: Digest auth. NDMS 4.x+: x-ndw2-interactive (session-based).
    # Try unauthenticated first — some RCI endpoints may return data without auth.
    RCI_VER=$(curl -s "http://${ROUTER_IP}:80/rci/show/version" 2>/dev/null | head -c 500)
    if [ -n "$RCI_VER" ] && echo "$RCI_VER" | grep -q '"title"\|"release"\|"sandbox"'; then
        pass "/rci/show/version: responds with JSON (no auth needed)"
        sample "$RCI_VER" 5
    elif [ -n "$RCI_VER" ] && echo "$RCI_VER" | grep -q "401\|Authorization"; then
        pass "/rci/show/version: requires auth (expected on 4.x/5.x)"
    else
        warn "/rci/show/version: unexpected response"
        [ -n "$RCI_VER" ] && sample "$RCI_VER" 5
    fi
elif command -v wget >/dev/null 2>&1; then
    AUTH_HEADERS=$(wget -q -S -O /dev/null "http://${ROUTER_IP}:80/auth" 2>&1) || AUTH_HEADERS=""
    AUTH_CODE=$(echo "$AUTH_HEADERS" | awk '/HTTP\//{print $2}' | tail -1)
    if [ -n "$AUTH_CODE" ]; then
        pass "/auth response: HTTP $AUTH_CODE"
    else
        warn "/auth: no response (wget)"
    fi
else
    warn "curl/wget not available — skipping HTTP checks"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "6. IPTABLES MANGLE (client routing policy)"
# ══════════════════════════════════════════════════════════════════════

# Used by: api-router.lua system_clients() — reads _NDM_HOTSPOT_PREROUTING_MANGL
if command -v iptables >/dev/null 2>&1; then
    pass "iptables: found"
    MANGLE_OUT=$(iptables -t mangle -S _NDM_HOTSPOT_PREROUTING_MANGL 2>/dev/null) || MANGLE_OUT=""
    if [ -n "$MANGLE_OUT" ]; then
        pass "_NDM_HOTSPOT_PREROUTING_MANGL chain: exists"
        RULE_COUNT=$(echo "$MANGLE_OUT" | wc -l)
        pass "mangle rules: $RULE_COUNT"
        # Check for specific patterns we parse
        MARK_COUNT=$(echo "$MANGLE_OUT" | grep -c "MARK --set-xmark" 2>/dev/null || echo "0")
        CONNNDM_COUNT=$(echo "$MANGLE_OUT" | grep -c "CONNNDMMARK" 2>/dev/null || echo "0")
        printf "    MARK rules: %s, CONNNDMMARK rules: %s\n" "$MARK_COUNT" "$CONNNDM_COUNT"
        printf "  sample:\n"
        sample "$MANGLE_OUT" 10
    else
        warn "_NDM_HOTSPOT_PREROUTING_MANGL: chain not found (per-device routing unavailable)"
    fi
else
    fail "iptables: not found"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "7. IP ROUTING (core functionality)"
# ══════════════════════════════════════════════════════════════════════

# Used by: all geo-split scripts, api-router.lua (system_interfaces, system_clients)
for cmd in "ip -o link show" "ip -o -4 addr show" "ip rule show" "ip route show default"; do
    OUTPUT=$(eval "$cmd" 2>/dev/null) || OUTPUT=""
    if [ -n "$OUTPUT" ]; then
        pass "$cmd: works"
        sample "$OUTPUT" 5
    else
        fail "$cmd: empty/failed"
    fi
done

# ip route show table all (used for fwmark→dev resolution)
TABLE_ALL=$(ip route show table all 2>/dev/null | grep '^default' | head -5) || TABLE_ALL=""
if [ -n "$TABLE_ALL" ]; then
    pass "ip route show table all (defaults): works"
    sample "$TABLE_ALL" 5
else
    warn "ip route show table all: no default routes"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "8. LINUX /proc (system info — no stock dependency)"
# ══════════════════════════════════════════════════════════════════════

# Used by: api-router.lua system_info() — pure Linux, should always work
for procfile in /proc/sys/kernel/hostname /proc/uptime /proc/meminfo /proc/loadavg /proc/cpuinfo /proc/stat; do
    if [ -f "$procfile" ]; then
        pass "$procfile: exists"
    else
        fail "$procfile: NOT found"
    fi
done

# df -k /opt
DF_OUT=$(df -k /opt 2>/dev/null | awk 'NR==2{print $2,$3,$4}') || DF_OUT=""
if [ -n "$DF_OUT" ]; then
    pass "df -k /opt: $DF_OUT"
else
    warn "df -k /opt: failed"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "9. ENTWARE PACKAGES"
# ══════════════════════════════════════════════════════════════════════

# Check required packages for webui
# nginx binary check first (nginx-mod-lua pulls nginx as dependency)
if [ -x /opt/sbin/nginx ]; then
    NGINX_VER=$(/opt/sbin/nginx -v 2>&1 | head -1)
    pass "nginx binary: $NGINX_VER"
else
    fail "nginx binary: /opt/sbin/nginx not found"
fi
for pkg in nginx-mod-lua logrotate; do
    PKG_STATUS=$(opkg status "$pkg" 2>/dev/null | grep "Status:" | head -1) || PKG_STATUS=""
    if echo "$PKG_STATUS" | grep -q "installed"; then
        VER=$(opkg status "$pkg" 2>/dev/null | grep "Version:" | head -1 | awk '{print $2}')
        pass "$pkg: installed ($VER)"
    else
        fail "$pkg: NOT installed"
    fi
done

# Check for ip-full (needed for batch route loading)
for pkg in ip-full iproute2; do
    PKG_STATUS=$(opkg status "$pkg" 2>/dev/null | grep "Status:" | head -1) || PKG_STATUS=""
    if echo "$PKG_STATUS" | grep -q "installed"; then
        VER=$(opkg status "$pkg" 2>/dev/null | grep "Version:" | head -1 | awk '{print $2}')
        pass "$pkg: installed ($VER)"
        break
    fi
done

# ══════════════════════════════════════════════════════════════════════
hdr "10. NDM HOOK DIRECTORIES"
# ══════════════════════════════════════════════════════════════════════

# Used by: geo-split ndm-hook.sh (ifstatechanged), smartdns-redirect netfilter-hook.sh
NDM_BASE="/opt/etc/ndm"
if [ -d "$NDM_BASE" ]; then
    pass "ndm hook base: $NDM_BASE exists"
else
    fail "ndm hook base: $NDM_BASE NOT found"
fi

for hookdir in ifstatechanged.d netfilter.d wan.d ifipchanged.d schedule.d fs.d button.d usb.d; do
    if [ -d "$NDM_BASE/$hookdir" ]; then
        HOOK_COUNT=$(find "$NDM_BASE/$hookdir" -maxdepth 1 -type f -o -type l 2>/dev/null | wc -l)
        pass "$hookdir: exists ($HOOK_COUNT scripts)"
    else
        warn "$hookdir: NOT found"
    fi
done

# ══════════════════════════════════════════════════════════════════════
hdr "11. OPKG FEEDS & ENTWARE ENV"
# ══════════════════════════════════════════════════════════════════════

# Entware version and feed architecture
if [ -f /opt/etc/opkg.conf ]; then
    pass "opkg.conf: exists"
    FEED_ARCH=$(grep -o 'entware-[a-z0-9_]*' /opt/etc/opkg.conf 2>/dev/null | head -1)
    if [ -n "$FEED_ARCH" ]; then
        pass "feed arch: $FEED_ARCH"
    fi
    printf "  feeds:\n"
    sample "$(grep -v '^#' /opt/etc/opkg.conf 2>/dev/null | grep -v '^$')" 10
else
    fail "opkg.conf: not found (Entware not installed?)"
fi

# Entware base dir
if [ -d /opt/etc/init.d ]; then
    INIT_COUNT=$(find /opt/etc/init.d -maxdepth 1 -name "S*" 2>/dev/null | wc -l)
    pass "Entware init.d: $INIT_COUNT startup scripts"
else
    warn "Entware init.d: not found"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "12. ARCHITECTURE & KERNEL"
# ══════════════════════════════════════════════════════════════════════

ARCH=$(uname -m 2>/dev/null) || ARCH="unknown"
KERNEL=$(uname -r 2>/dev/null) || KERNEL="unknown"
pass "arch: $ARCH"
pass "kernel: $KERNEL"

# BusyBox version
BB_VER=$(busybox 2>&1 | head -1) || BB_VER="not found"
pass "busybox: $BB_VER"

# Shell check (ash has no --version; use readlink to identify)
if [ -L /opt/bin/sh ]; then
    SHELL_TARGET=$(readlink /opt/bin/sh 2>/dev/null || echo "unknown")
    pass "/opt/bin/sh -> $SHELL_TARGET"
elif [ -f /opt/bin/sh ]; then
    pass "/opt/bin/sh: exists (not a symlink)"
else
    warn "/opt/bin/sh: not found"
fi

# ══════════════════════════════════════════════════════════════════════
hdr "SUMMARY"
# ══════════════════════════════════════════════════════════════════════

printf "\n"
printf "  ✓ Pass: %d\n" "$OK"
printf "  ⚠ Warn: %d\n" "$WARN"
printf "  ✗ Fail: %d\n" "$FAIL"
printf "\n"

if [ "$FAIL" -eq 0 ]; then
    printf "  All critical checks passed. webui should work.\n"
elif [ "$FAIL" -le 2 ]; then
    printf "  Minor issues detected. webui /custom/ dashboard may work with workarounds.\n"
else
    printf "  Multiple failures. webui likely needs adaptation for this firmware.\n"
fi
printf "\n  Please share this full output for analysis.\n\n"
 

 

2 hours ago, KeenTaur said:

С intel.com у меня ситуация как и у dimsok.

(по последним данным не работает только первая страница, загрузки работают.)
Но вопрос интересный...
 

2 hours ago, KeenTaur said:

Скриншот с сайтом и трассировкой через туннель и url. В конфигурации geoip сейчас только гео-зона ru.

Все говорит о том, что vps кривоват...
 

Spoiler

Анализ проблемы KeenTaur с intel.com

Tracert из скриншота (Windows)

tracert -d www.intel.com
→ e7842.dsca.akamaiedge.net [23.210.254.130]

Hop 1:  10.5.33.1      ← роутер (LAN gateway)
Hop 2:  10.236.17.0    ← VPN туннель (RFC1918, 10.0.0.0/8)
Hop 3:  172.29.172.1   ← VPN-сервер внутренняя сеть (RFC1918, 172.16.0.0/12)
Hop 4:  100.100.1.1    ← CGNAT российского ISP (RFC 6598, 100.64.0.0/10)

Диагноз

VPN-туннель выходит в Россию. Hop 4 (100.100.1.1) — это CGNAT (Carrier-Grade NAT по RFC 6598, диапазон 100.64.0.0/10), характерный для российских ISP (Билайн, МТС, Ростелеком).

Цепочка:

  1. www.intel.com резолвится в 23.210.254.130 (Akamai CDN, NetName: AIBV, Нидерланды)
  2. IP 23.210.254.130 не в российских GeoIP-диапазонах → geo-split НЕ маршрутизирует его через ISP
  3. Трафик идёт через default route → VPN-туннель
  4. VPN-туннель выходит через российский ISP (CGNAT 100.100.x.x)
  5. Akamai видит запрос с российского IP → Intel перенаправляет на ru-banner.html (санкционная страница)

Это НЕ баг geo-split

Geo-split работает корректно:

  • Российские IP → маршрутизируются через ISP напрямую (таблицы 1000/1001)
  • Все остальные IP (включая Akamai 23.210.254.130) → default route → VPN

Проблема в том, что VPN-сервер сам находится в России (или выходит через российского провайдера). Intel.com увидит российский IP независимо от того, идёт трафик напрямую или через VPN.

Про "intel.com без www не открывается"

Отдельная проблема: intel.com (без www) делает HTTP 301 redirect → www.intel.com. Если редирект сам идёт через российский exit-point, Intel сразу перебрасывает на ru-banner.html ещё до загрузки основного сайта.

Что делать

Вариант Описание
1. VPN за рубежом Нужен VPN-сервер с exit node ВОВНЕ России (Германия, Нидерланды, Финляндия). Без этого маршрутизация через VPN бессмысленна для обхода гео-блокировок
2. Добавить intel.com в домены Если у пользователя ЕСТЬ второй VPN (за границей) или ISP выдаёт нероссийский IP — добавить intel.com / www.intel.com в domains.txt, чтобы трафик шёл через ISP
3. Понять назначение VPN VPN в России имеет смысл для шифрования трафика от ISP или обхода DPI — но НЕ для обхода гео-блокировок иностранных сайтов

Вывод

Geo-split с GEO_ZONE=ru маршрутизирует российские сайты через ISP. Для иностранных сайтов (intel.com, Akamai CDN) трафик идёт через VPN — но если VPN сам выходит в России, проблема не решается. KeenTaur нужен VPN с exit node за пределами РФ.


🤷‍♂️

Опубликовано
27 minutes ago, RWH said:

@KeenTaur, у меня нет такой проблемы, нормально открывается intel.com

+1

⚠️ хинт:
В продаже существуют типа иностранные vps которые не совсем иностранные...
Цена в ~250 руб/мес - верный признак, нормальные где-то от 500-600 руб и более

Опубликовано (изменено)

А ещё бывает так:
 

Spoiler

Что известно

  • VPN exit IP#.#.#.#
  • whatismyipaddress.com: Helsinki, Finland, *** ✓
  • Netflix: работает ✓
  • ChatGPT: работает ✓
  • Intel.com: показывает санкционный баннер ✗

WHOIS анализ IP #.#.#.#

Уровень 1 — /24 sub-allocation (IPXO marketplace):
  inetnum:   #.#.#.# - #.#.#.#
  netname:   ipxo                          ← IP leasing marketplace!
  country:   FI

Уровень 2 — /15 parent block (оригинальный владелец):
  inetnum:   #.#.#.# - #.#.#.#
  netname:   US-***
  org-name:  ***
  country:   US

Уровень 3 — AS announcement:
  ***:       ***-AS
  org-name:  ****
  country:   PL (***, Польша)

Корневая причина: IPXO leased IP с "грязной" репутацией

IPXO — это маркетплейс аренды IP-адресов. Владелец блока (***, US) сдаёт /24 через IPXO, арендатор (***, Польша) анонсирует его через AS*** из финского датацентра.

Проблема: разные GeoIP-базы классифицируют этот IP по-разному:

Сервис GeoIP база Результат
whatismyipaddress.com RIPE WHOIS + BGP Finland ✓
Netflix Собственная + MaxMind Finland ✓ (или не блокирует FI)
ChatGPT/OpenAI Вероятно MaxMind Finland ✓
Intel.com (*** CDN) *** GeoIP 🇷🇺 Russia ✗

И как результат - нет интела (первой страницы)

Изменено пользователем 0xkee
Опубликовано (изменено)
3 часа назад, KeenTaur сказал:

С intel.com у меня ситуация как

intel.se добавляли? Мне помогло обойти заглушку, хоть и резолвятся на одном и том же ip. И ЧВС у меня не в Швеции :) 

UPD. Зато DoT и DoH в Швеции. 

Изменено пользователем mrGhotius
Опубликовано (изменено)

Вопрос:
Нужен ли функционал для проверки качества туннеля/ей, на прикладном уровне («end-to-end accessibility check») - какие ресурсы (сайты и тд) доступны через туннель, а какие нет?

Какой-то общий список + персональные настройки.

Поставьте, пожалуйста, этому сообщению upvote, если надо.

Изменено пользователем 0xkee

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...

Важная информация

На этом сайте используются файлы cookie. Нажимая "Я принимаю" или продолжая просмотр сайта, вы разрешаете их использование: Политика конфиденциальности.