From 04af8cb00629df0d11056243b5bee86c9d158b6e Mon Sep 17 00:00:00 2001 From: pyrschtjag Date: Sat, 14 Feb 2026 19:52:31 +0400 Subject: [PATCH] update_0.1 --- README.md | 157 ++++++++++++++++++++++++++++++ config/xray-manager | 77 +++++++++++++++ init.d/xray-manager | 27 ++++++ xray-manager.sh | 231 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 492 insertions(+) create mode 100644 README.md create mode 100644 config/xray-manager create mode 100644 init.d/xray-manager create mode 100644 xray-manager.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..7856c5b --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# xray-manager + +**xray-manager** — это легковесный Shell-скрипт (Ash) для OpenWrt, предназначенный для автоматического управления списками маршрутизации (IP-адреса и домены) через `nftables` и `tproxy`. + +Скрипт позволяет гибко распределять трафик: направлять нужные ресурсы через Xray, создавать исключения и автоматически поддерживать списки в актуальном состоянии. + +## Основные функции + +* **Мульти-ссылочные секции**: Возможность указать несколько URL в одной категории — скрипт сам скачает их, объединит и удалит дубликаты. +* **Поддержка `auto-merge`**: Автоматическое решение конфликтов пересекающихся подсетей на уровне ядра (nftables), что предотвращает ошибки при загрузке больших списков. +* **Умное обновление DNS**: Сервис `dnsmasq` перезапускается только при реальном изменении списков доменов, минимизируя разрывы соединений. +* **Проверка изменений (Hashing)**: Скрипт отслеживает изменения в конфигурации URL и уведомляет о необходимости запуска обновления при использовании `reload`. +* **Гибкая маршрутизация**: Поддержка фильтрации по источнику (`src`) и назначению (`dst`), а также корректная обработка локального трафика роутера (`route_local`). +* **Автоматическая очистка**: Удаление устаревших кэш-файлов и правил при изменении структуры конфигурации. + +## Установка + +### 1. Зависимости +Убедитесь, что установлены необходимые пакеты: +```bash +opkg update && opkg install kmod-nf-tproxy kmod-nft-tproxy git git-http curl +``` +Для полноценной работы со списками доменов необходима полная версия dnsmasq: +```bash +opkg remove dnsmasq +echo "nameserver 1.1.1.1" > /etc/resolv.conf +opkg install dnsmasq-full +``` +### 2. Клонирование репозитория + +Рекомендуется устанавливать в папку /opt: +```bash +git clone gitea@gitea.lan:pyrschtjag/xray-manager.git /opt/.xray-manager +``` +или +```bash +git clone https://gitea.oyacoi.ru/pyrschtjag/xray-manager.git /opt/.xray-manager +``` +### 3. Создание симлинков + +Для интеграции скрипта в систему выполните следующие команды: +```Bash +# Конфигурационный файл +ln -s /opt/.xray-manager/config/xray-manager /etc/config/xray-manager +# Инит-скрипт для управления сервисом +ln -s /opt/.xray-manager/init.d/xray-manager /etc/init.d/xray-manager +# Команда в PATH для быстрого доступа из любой папки +ln -s /opt/.xray-manager/xray-manager.sh /usr/sbin/xray-manager +``` + +## Использование + +Скрипт поддерживает стандартные команды OpenWrt: + +* **xray-manager start** — Инициализация таблиц и применение текущих списков из кэша. +* **xray-manager reload** — Применение изменений из конфига без скачивания URL. Если ссылки изменились, скрипт выведет предупреждение. +* **xray-manager update** — Скачивание всех внешних списков, дедупликация и обновление правил. +* **xray-manager stop** — Полная очистка таблиц nftables и удаление временных файлов. +* **xray-manager restart** — Остановка и повторный запуск. + +## Конфигурация + +### Глобальные параметры + +Секция config xray-manager 'main' (обязательна): + +```bash +config xray-manager 'main' + option table_name 'xray' # Имя таблицы в nftables + option tproxy_port '61219' # Порт Xray (TProxy) + option tproxy_mark '10' # fwmark для трафика + option timeout '1h' # Время жизни (для nftables set) + option ipnet_dir '/tmp/ipnet' # Путь к спискам IP + option dnsmasq_dir '/tmp/dnsmasq.d' # Путь к конфигам dnsmasq + option cache_dir '/tmp/.xray-manager/cache' + option active_list '/tmp/.xray-manager/active_files' + option route_local '1' # Проксировать трафик самого роутера +``` + +### Исключения (Whitelist) + +Секция lnet для защиты локальных адресов. Требуется для подстраховки, так как некоторые ресурсы могут подменять реальный IP на localhost при резолвинге: + +```bash +config xray-list 'lnet' + option name 'whitelist' + option direction 'dst' + option exclude '1' + list ip '127.0.0.0/8' + list ip '10.0.0.0/8' + list ip '172.16.0.0/12' + list ip '192.168.0.0/16' + list ip '169.254.0.0/16' + list ip '100.64.0.0/10' + list ip '224.0.0.0/4' + list ip '255.255.255.255/32' + list ip '0.0.0.0/8' +``` + +### Списки маршрутов + +Каждая секция `config xray-list` определяет, какой именно трафик будет перехвачен или пропущен. + +| Параметр | Значение | Описание | +| :--- | :--- | :--- | +| **name** | `string` | Уникальное имя. Используется для названия сета в `nftables` (например, `@v2ray_udom`) и имени файла в `/tmp`. | +| **direction** | `dst` или `src` | **Направление фильтрации**.
`dst`: проверка IP назначения (куда идем).
`src`: проверка IP отправителя (кто идет). | +| **exclude** | `0` или `1` | **Тип правила**.
`0`: Направлять трафик в прокси.
`1`: Пропускать трафик напрямую (исключение). | + +### Типы списков (list) + +Внутри каждой секции можно комбинировать разные типы данных: + +* **list ip**: Одиночный IP-адрес или подсеть в формате CIDR (например, `1.1.1.1` или `192.168.1.0/24`). Заносится напрямую в `nftables`. +* **list domain**: Одиночный домен. Скрипт создаст файл конфигурации для `dnsmasq`, который будет перехватывать IP-адреса этого домена при резолвинге и динамически добавлять их в соответствующий сет `nftables`. +* **list url_ip**: Ссылка на внешний текстовый файл со списком IP/подсетей. Скрипт скачает, очистит от комментариев и объединит данные. +* **list url_domain**: Ссылка на список доменов. Аналогично `url_ip`, но данные готовятся для обработки через `dnsmasq`. + +#### Пример 1: Направление доменов в прокси (назначение): + +```bash +config xray-list + option name 'v2ray' + option direction 'dst' + option exclude '0' + list url_domain '[https://raw.githubusercontent.com/.../spotify](https://raw.githubusercontent.com/.../spotify)' + list url_domain '[https://raw.githubusercontent.com/.../openai](https://raw.githubusercontent.com/.../openai)' + list domain 'private.example.com' +``` + +#### Пример 2: Направление целой подсети локальных клиентов в прокси (источник): + + +```bash +config xray-list + option name 'local_network' + option direction 'src' + option exclude '0' + list ip '10.1.0.0/24' +``` + +#### Пример 3: Исключение конкретного устройства из проксирования: +```bash +config xray-list + option name 'my_pc_bypass' + option direction 'src' + option exclude '1' + list ip '10.1.0.6' +``` + +## Логика работы (Flow) + +1. **Цепочка Prerouting**: Весь входящий трафик от клиентов попадает в таблицу `nftables`. +2. **Исключения (Exclude 1)**: Сначала проверяются списки с `exclude '1'`. Если совпадение найдено, пакет помечается `accept` и уходит по обычному маршруту. +3. **Маркировка (Direction src)**: Если IP клиента найден в списке с `direction 'src'`, пакет помечается меткой `0x66`. +4. **Перехват (Direction dst)**: Для пакетов с меткой `0x66` проверяются списки назначений (`dst`). Если финальный IP найден в сете — пакет перенаправляется на локальный порт TProxy. +5. **DNS-фильтрация**: При запросе домена из списка `domain`/`url_domain`, `dnsmasq` сохраняет полученный IP в сет `nftables` на время, указанное в `option timeout`. \ No newline at end of file diff --git a/config/xray-manager b/config/xray-manager new file mode 100644 index 0000000..f25ccca --- /dev/null +++ b/config/xray-manager @@ -0,0 +1,77 @@ +config xray-manager 'main' + option table_name 'xray' + option tproxy_port '61219' + option tproxy_mark '10' + option timeout '1h' + option ipnet_dir '/tmp/ipnet' + option dnsmasq_dir '/tmp/dnsmasq.d' + option cache_dir '/tmp/.xray-manager/cache' + option active_list '/tmp/.xray-manager/active_files' + option route_local '1' + +config xray-list 'lnet' + option name 'whitelist' + option direction 'dst' + option exclude '1' + list ip '127.0.0.0/8' + list ip '10.0.0.0/8' + list ip '172.16.0.0/12' + list ip '192.168.0.0/16' + list ip '169.254.0.0/16' + list ip '100.64.0.0/10' + list ip '224.0.0.0/4' + list ip '255.255.255.255/32' + list ip '0.0.0.0/8' + list ip '192.0.0.0/24' + list ip '192.0.2.0/24' + list ip '198.18.0.0/15' + list ip '198.51.100.0/24' + list ip '203.0.113.0/24' + +config xray-list + option list 'exclude' + option direction 'dst' + option exclude '1' + list domain 'bf2hub.com' + +config xray-list + option name 'local' + option direction 'src' + option exclude '0' + list ip '10.1.0.0/24' + list ip '10.2.0.0/24' + +config xray-list + option name 'local_exclude' + option direction 'src' + option exclude '1' + list ip '10.1.0.6' + +config xray-list + option name 'refilter' + option direction 'dst' + option exclude '0' + list url_ip 'https://raw.githubusercontent.com/1andrevich/Re-filter-lists/refs/heads/main/community_ips.lst' + list url_ip 'https://raw.githubusercontent.com/1andrevich/Re-filter-lists/refs/heads/main/discord_ips.lst' + list url_ip 'https://raw.githubusercontent.com/1andrevich/Re-filter-lists/refs/heads/main/ipsum.lst' + +config xray-list + option name 'cdn' + option direction 'dst' + option exclude '0' + list url_ip 'https://raw.githubusercontent.com/123jjck/cdn-ip-ranges/refs/heads/main/all/all_plain_ipv4.txt' + +config xray-list + option name 'telegram' + option direction 'dst' + option exclude '0' + list url_ip 'https://raw.githubusercontent.com/fernvenue/telegram-cidr-list/refs/heads/master/CIDRv4.txt' + +config xray-list + option name 'v2ray' + option direction 'dst' + option exclude '0' + list url_domain 'https://raw.githubusercontent.com/v2ray/domain-list-community/refs/heads/master/data/spotify' + list url_domain 'https://raw.githubusercontent.com/v2ray/domain-list-community/refs/heads/master/data/microsoft' + list url_domain 'https://raw.githubusercontent.com/v2ray/domain-list-community/refs/heads/master/data/openai' + \ No newline at end of file diff --git a/init.d/xray-manager b/init.d/xray-manager new file mode 100644 index 0000000..d8de036 --- /dev/null +++ b/init.d/xray-manager @@ -0,0 +1,27 @@ +#!/bin/sh /etc/rc.common + +START=95 +USE_PROCD=1 + +extra_command "update" "Download and refresh remote lists" + +start_service() { + xray-manager start + + procd_open_instance + procd_set_param command tail -f /dev/null + procd_set_param respawn + procd_close_instance +} + +stop_service() { + xray-manager stop +} + +reload_service() { + xray-manager reload +} + +update() { + xray-manager update +} diff --git a/xray-manager.sh b/xray-manager.sh new file mode 100644 index 0000000..02e5a9b --- /dev/null +++ b/xray-manager.sh @@ -0,0 +1,231 @@ +#!/bin/sh + +. /lib/functions.sh + +_log() { + local msg="$1" + local level="${2:-info}" + local to_console="${3:-0}" + logger -t xray-manager -p "daemon.$level" "$msg" + [ "$to_console" = "1" ] && echo "[$level] $msg" +} + +_get_md5() { echo "$1" | md5sum | cut -d' ' -f1; } + +_nft_init() { + [ -z "$TABLE" ] && return 1 + nft add table ip "$TABLE" 2>/dev/null + nft add chain ip "$TABLE" prerouting { type filter hook prerouting priority mangle \; policy accept \; } 2>/dev/null + nft add chain ip "$TABLE" output { type route hook output priority mangle \; policy accept \; } 2>/dev/null + nft flush chain ip "$TABLE" prerouting + nft flush chain ip "$TABLE" output + + if [ "$ROUTE_LOCAL" = "1" ]; then + [ -z "$TPROXY_MARK" ] && TPROXY_MARK="10" + local mark_hex=$(printf '0x%x' "$TPROXY_MARK") + if ! ip route show table 110 | grep -q "local default"; then + ip route add local default dev lo table 110 + fi + if ! ip rule show | grep -q "fwmark $mark_hex lookup 110"; then + ip rule add fwmark "$TPROXY_MARK" table 110 + fi + fi +} + +_nft_apply_data() { + local sname="$1" file="$2" exc="$3" dir="$4" type="$5" changed="$6" + [ -z "$TABLE" ] && return 1 + + local flags="interval" + [ "$type" = "dom" ] || [ "$type" = "udom" ] && flags="interval,timeout" + + nft add set ip "$TABLE" "$sname" { type ipv4_addr \; flags $flags \; auto-merge \; } 2>/dev/null + + if [ "$changed" = "1" ] && [ -s "$file" ] && { [ "$type" = "ip" ] || [ "$type" = "uip" ]; }; then + _log "Set '$sname' data refreshed in NFT" + nft flush set ip "$TABLE" "$sname" + awk '{print "add element ip '$TABLE' '$sname' { " $1 " }"}' "$file" | nft -f - + fi + + if [ "$exc" = "1" ]; then + local selector="ip daddr" + [ "$dir" = "src" ] && selector="ip saddr" + nft insert rule ip "$TABLE" prerouting $selector "@$sname" accept + [ "$ROUTE_LOCAL" = "1" ] && nft insert rule ip "$TABLE" output $selector "@$sname" accept + elif [ "$dir" = "src" ]; then + nft add rule ip "$TABLE" prerouting ip saddr "@$sname" ct mark set 0x66 + [ "$ROUTE_LOCAL" = "1" ] && nft add rule ip "$TABLE" output ip saddr "@$sname" ct mark set 0x66 + else + for proto in tcp udp; do + nft add rule ip "$TABLE" prerouting ct mark 0x66 ip daddr "@$sname" meta l4proto $proto tproxy to 127.0.0.1:"$TPROXY_PORT" meta mark set "$TPROXY_MARK" accept + [ "$ROUTE_LOCAL" = "1" ] && nft add rule ip "$TABLE" output ct mark 0x66 ip daddr "@$sname" meta l4proto $proto meta mark set "$TPROXY_MARK" accept + done + fi +} + +_process_item() { + local id="$1" val="$2" type="$3" dir="$4" exc="$5" + local fullpath hfile changed=0 + + [ -z "$DNSMASQ_DIR" ] || [ -z "$IPNET_DIR" ] && return 1 + + [ "$type" = "dom" ] || [ "$type" = "udom" ] && fullpath="$DNSMASQ_DIR/$id.lst" || fullpath="$IPNET_DIR/$id.lst" + hfile="$CACHE_DIR/$id.hash" + + echo "$fullpath" >> "$ACTIVE_LIST" + echo "$hfile" >> "$ACTIVE_LIST" + + # 1. Сначала считаем хэш текущего конфига + local new_hash=$(_get_md5 "$val") + local old_hash=$(cat "$hfile" 2>/dev/null) + + # 2. ОДНА проверка для режима reload/start (SKIP_URL) + if [ "$SKIP_URL" = "1" ] && { [ "$type" = "uip" ] || [ "$type" = "udom" ]; }; then + # Проверяем, не изменился ли список ссылок в UCI + if [ "$new_hash" != "$old_hash" ]; then + _log "$id: Configuration changed (URLs modified). Please run 'update' to sync!" "warn" "1" + fi + + if [ -f "$fullpath" ]; then + _nft_apply_data "$id" "$fullpath" "$exc" "$dir" "$type" "0" + return + else + _log "$id: File not found. Run 'update' to download." "warn" "1" + return + fi + fi + + # 3. Режим UPDATE (или обработка локальных списков) + if [ "$new_hash" != "$old_hash" ] || [ ! -f "$fullpath" ]; then + changed=1 + case "$type" in + ip) + echo "$val" | tr ' ' '\n' | sed 's/\r//g; /^#/d; /^[[:space:]]*$/d; /\//! s|$|/32|' | sort -u > "$fullpath" + ;; + dom) + echo "$val" | tr ' ' '\n' | tr '[:upper:]' '[:lower:]' | grep -oE '([a-z0-9-]+\.)+[a-z]{2,}' | sort -u | \ + sed "s|^|nftset=/|; s|\$|/4#ip#$TABLE#$id|" > "$fullpath" + DNS_CHANGES=1 ;; + uip|udom) + _log "Processing $id (multiple URLs)..." "info" + local tmp_all="/tmp/dl_all_$id" + : > "$tmp_all" + + for url in $val; do + local tmp_part="/tmp/dl_part" + _log "Downloading part from $url" "info" + if curl -sSfL --connect-timeout 10 "$url" -o "$tmp_part"; then + cat "$tmp_part" >> "$tmp_all" + echo "" >> "$tmp_all" + rm -f "$tmp_part" + else + _log "Failed to download: $url" "err" "1" + fi + done + + if [ -s "$tmp_all" ]; then + local out="/tmp/out_$id" + if [ "$type" = "uip" ]; then + sed 's/\r//g; /^#/d; /^[[:space:]]*$/d; /\//! s|$|/32|' "$tmp_all" | sort -u > "$out" + else + tr '[:upper:]' '[:lower:]' < "$tmp_all" | grep -oE '([a-z0-9-]+\.)+[a-z]{2,}' | sort -u | \ + sed "s|^|nftset=/|; s|\$|/4#ip#$TABLE#$id|" > "$out" + DNS_CHANGES=1 + fi + mv "$out" "$fullpath" + rm -f "$tmp_all" + changed=1 + else + _log "All downloads for $id failed" "err" "1" + rm -f "$tmp_all" + return + fi + ;; + esac + echo "$new_hash" > "$hfile" + fi + _nft_apply_data "$id" "$fullpath" "$exc" "$dir" "$type" "$changed" +} + +_process_section() { + local s="$1" name dir exc + config_get name "$s" name + config_get dir "$s" direction "dst" + config_get exc "$s" exclude "0" + [ -z "$name" ] && return + local ips doms uips udoms + _append_list() { CURRENT_VAL="${CURRENT_VAL}${CURRENT_VAL:+ }$1"; } + CURRENT_VAL=""; config_list_foreach "$s" ip _append_list; ips="$CURRENT_VAL" + CURRENT_VAL=""; config_list_foreach "$s" domain _append_list; doms="$CURRENT_VAL" + CURRENT_VAL=""; config_list_foreach "$s" url_ip _append_list; uips="$CURRENT_VAL" + CURRENT_VAL=""; config_list_foreach "$s" url_domain _append_list; udoms="$CURRENT_VAL" + [ -n "$(echo "$ips" | xargs)" ] && _process_item "${name}_ip" "$ips" "ip" "$dir" "$exc" + [ -n "$(echo "$doms" | xargs)" ] && _process_item "${name}_dom" "$doms" "dom" "$dir" "$exc" + [ -n "$(echo "$uips" | xargs)" ] && _process_item "${name}_uip" "$uips" "uip" "$dir" "$exc" + [ -n "$(echo "$udoms" | xargs)" ] && _process_item "${name}_udom" "$udoms" "udom" "$dir" "$exc" +} + +_cleanup_orphans() { + [ -z "$IPNET_DIR" ] || [ -z "$DNSMASQ_DIR" ] && return + for f in "$IPNET_DIR"/* "$DNSMASQ_DIR"/*; do + [ -e "$f" ] || continue + if ! grep -Fxq "$f" "$ACTIVE_LIST" 2>/dev/null; then + local base="${f##*/}" + _log "Removing orphan: $base" "info" + [ "$(dirname "$f")" = "$DNSMASQ_DIR" ] && DNS_CHANGES=1 + rm -f "$f" "$CACHE_DIR/${base%.*}.hash" "$CACHE_DIR/${base%.*}.tstamp" + fi + done + rm -f "$ACTIVE_LIST" +} + +_load_config() { + config_load xray-manager + TABLE=$(uci -q get xray-manager.@xray-manager[0].table_name) + TPROXY_PORT=$(uci -q get xray-manager.@xray-manager[0].tproxy_port) + TPROXY_MARK=$(uci -q get xray-manager.@xray-manager[0].tproxy_mark) + IPNET_DIR=$(uci -q get xray-manager.@xray-manager[0].ipnet_dir) + DNSMASQ_DIR=$(uci -q get xray-manager.@xray-manager[0].dnsmasq_dir) + CACHE_DIR="/tmp/xray-manager.cache" + ACTIVE_LIST="/tmp/xray_active_files" + ROUTE_LOCAL=$(uci -q get xray-manager.@xray-manager[0].route_local) + [ -z "$IPNET_DIR" ] || [ -z "$DNSMASQ_DIR" ] && return 1 + mkdir -p "$IPNET_DIR" "$DNSMASQ_DIR" "$CACHE_DIR" + if ! uci show dhcp.@dnsmasq[0].confdir 2>/dev/null | grep -q "'$DNSMASQ_DIR'"; then + uci add_list dhcp.@dnsmasq[0].confdir="$DNSMASQ_DIR" + uci commit dhcp + DNS_CHANGES=1 + fi +} + +_run() { + DNS_CHANGES=0 + _load_config || return 1 + : > "$ACTIVE_LIST" + _nft_init || return 1 + local lnet_val=$(uci -q get xray-manager.@lnet[0].ip) + [ -n "$lnet_val" ] && _process_item "lnet" "$lnet_val" "ip" "dst" "1" + config_foreach _process_section "xray-list" + _cleanup_orphans + if [ "$DNS_CHANGES" = "1" ]; then + /etc/init.d/dnsmasq restart + fi +} + +_stop() { + _load_config || exit 1 + nft delete table ip "$TABLE" 2>/dev/null + ip rule del fwmark "$TPROXY_MARK" table 110 2>/dev/null + ip route del local default dev lo table 110 2>/dev/null + rm -rf "$IPNET_DIR"/* "$DNSMASQ_DIR"/* "$CACHE_DIR"/* + _log "Xray-manager stopped." "info" "1" + /etc/init.d/dnsmasq restart +} + +case "$1" in + start|reload) export SKIP_URL=1; _run ;; + update) export SKIP_URL=0; _run ;; + stop) _stop ;; + restart) _stop; export SKIP_URL=1; _run ;; + *) echo "Usage: $0 {start|reload|update|stop|restart}" ;; +esac \ No newline at end of file