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

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

Опубликовано (изменено)
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
  • Ответов 112
  • Создана
  • Последний ответ

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

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

Здравствуйте. Спасибо за приложение, очень удобно всё сделано. Единственное не могу настроить 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
Опубликовано
12 часов назад, mrGhotius сказал:

intel.se добавляли?

да вроде с конфигурацией "геозона ru напрямую через провайдера, все остальное в туннель" не требуется.

 

Склонен думать, что туннель хоть и выходит, ну пусть будет, в /Средней/ Азии, но интелу не нравится этот облачный провайдер.

Остальное, в основном, для @0xkee

13 часов назад, 0xkee сказал:

VPN-туннель выходит через российский ISP (CGNAT 100.100.x.x)

Вот с выделенным не согласен. Просмотрел несколько сервисов whois, диапазон 100.64.0.0 - 100.127.255.255 это Shared Address Space (Общее адресное пространство), которое "может использоваться только в сетях поставщиков услуг или на маршрутизирующем оборудовании, которое способно выполнять преобразование адресов между интерфейсами маршрутизатора, когда адреса идентичны на двух разных интерфейсах". Про российских ISP не увидел ничего. Просто внутренняя кухня. Дальше в моей трассировке были бы видны реальные адреса провадера, потому она и прервана.

 

По web-ui

13 часов назад, 0xkee сказал:

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

Глянул листинг скрипта :) Гм, ниче-се проще :) Но я постараюсь, но попозже. Спасибо!

А начну вот с этого:

13 часов назад, 0xkee сказал:

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

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

Не подходит, или я не знаю как. В 2.16 Keenetic в веб-интерфейсе не видит awg0 с asc2.0, созданного в Entware. Механизм opkgtun появился позже.

13 часов назад, 0xkee сказал:

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

а вот это посмотрю-попробую. Бланодарю!

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

Вот с выделенным не согласен. ... Дальше в моей трассировке были бы видны реальные адреса провадера, потому она и прервана.

Ок, 

тут ещё тема всплыла, браузеры типа хрома (а это почти все), сильно кешируют dns иногда даже используют свои...
тот же интел лучше ещё попроверять с другого браузера/компа/телефона в этой же сети... возможно конкретный браузер ещё не "отлип" от старого cdn

Опубликовано
5 минут назад, 0xkee сказал:

тот же интел лучше ещё попроверять с другого браузера/компа/телефона в этой же сети... возможно конкретный браузер ещё не "отлип" от старого cdn

Проблема в проверках интела, он явно считает доступные мне vps нерукопожатными :) geoip не при чем. Кроме этого роутера и ноута за ним, я проверил с утра через туннели, установленные прямо со смартфона в режиме весь трафик через туннели на двух vps с разными внешними адресами, но у одного провайдера.

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

Доброго времени суток!

Пара вопросов:

1) Будет ли поддержка Netcraze 5.1.1? Ради интереса обновил прошивку. На данный момент всё работает, кроме вебки

2) Если добавлять домены в domains.txt, то для полного исключения достаточно так написать

*.dion.vc

Или без wildcard также домен нужно указать?

dion.vc
*.dion.vc
Опубликовано (изменено)
1 час назад, cryoPanda сказал:

1) Будет ли поддержка Netcraze 5.1.1? Ради интереса обновил прошивку. На данный момент всё работает, кроме вебки

 

@cryoPanda все модули прекрасно работают, попробуйте переустановить модуль WebUI через деинсталяцию и новую установку 

Спойлер

image.thumb.jpeg.f2dac1cb2aaa022a84edaea4094e4eb4.jpeg

Ответ на второй вопрос находится по следующему пути
/opt/keenetic-entware-extras/geo-split-data/lists/ru-whitelist.txt
 

Спойлер

 

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

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

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

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

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

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

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

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

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

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

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

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

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