Vless sing-box + nfqws zapret на OpenWRT роутере

Привет! Хочу в домашней сети настроить всё так, чтобы отдельные клиенты трогать никогда не приходилось, но трафик в итоге ходил оптимально на уровне роутера:

  1. Если блокировка со стороны зарубежных серверов, тогда трафик идёт через vless reality на зарубежный VPS
  2. Если блокировка изнутри, тогда трафик модифицируется bol-van/zapret через nfqws
  3. Иначе трафик чистый

На OpenWRT-роутере установлены и настроены zapret (nfqws) и sing-box.
sing-box запускает inbounds tun, а в route/rules есть правила которые часть трафика перенаправляют в outbounds/vless.

По отдельности оба сервиса работают. Вместе - нет: перестаёт давать результат zapret, хотя он остаётся запущенным.

В сетях понимаю мало, но видимо пришла пора разобраться.
Прошу совету у знающих - вообще возможно ли достичь желаемого?
И если да, то как это принципиально должно работать?

Дополнение: sing-box настраивал по этой статье Настройка sing-box (openwrt) – Telegraph

Привет!
У тебя получилось подружить оба сервиса?

Прогресс нулевой и времени заниматься этим к сожалению сейчас нет. Но если что-то сделаю, обязательно напишу и здесь.

На всякий случай оставлю здесь релевантные темы, может кого-то наведёт на мысль.
И было бы хорошо, если бы он этой мыслью поделился, потому что я пока даже не понимаю в каком направлении двигаться.

Вобщем openvpn тоже работает с Masquarde -eth0, больше ничего не надо.
Для сокрытия openvpn я когда-то использовал stunnel в режиме точка-точка.


это делается либо через policy routing на linux по IP адресам,
либо через высокоуровневые прокси-решения типа xray - по доменам


Роутер с OpenWRT. У меня установлен пакет pbr, добавлен интерфейс Wireguard.
Согласно правилам pbr траффик на определенные в списке домены идет через
интерфейс Wireguard, остальной траффик - напрямую через Wan

https://openwrt.org/docs/guide-user/network/routing/pbr

Мне на днях должен роутер прийти, буду тоже ковыряться. Хотел сделать то же самое что и вы, может быть скорее zapret с фоллбеком на прокси. Есть ощущение, что как будто проблема должна решиться добавлением сетевого интерфейса прокладки, хотя опыта у меня тоже немного.

По поводу части с vless realty, у меня установлен yichya/luci-app-xray: (Almost) full feature Xray client for OpenWrt, добавлен сервер на первой вкладке, все остальное отключено, inbounds и lan host access все пустое должно быть, во вкладке dns убирается все что предзаполнено и настраиваются чисто основной и дополнительный днс, во вкладке fakedns все поля пустые, внизу создаёшь правила по сатам, либо через geosite если уже есть профиль в v2ray-geosite, либо domain как например этот форум, во вкладке outbound routing опять же все предустановленное удаляешь, Default Ports Policy bypass ставишь и указываешь порты, у меня тсп 80, 443, 853, удп 53 и 443, все остальное можно не трогать. Один прикол есть, при добавлении сервака в [vless][reality] Server Name должен быть домен сайта в одном виде, в панельке например (3)x-ui можно указать с www и без, но должно быть что-то одно, с двумя указанными в настройке клиента работать не будет. Так же можно свой список geosite собрать по инструкции из репозитория там же. И так и не понял почему, без отключения клиента, opkg в openwrt не может нормально к своим репозиториям подключится соответственно отключать на время обновления или установки пакетов.

У меня вроде получилось сделать то, что ты хочешь. У самого Keenetic Giga-1012.

Установлен XKeen + nfqws zapret

запрет настраивал по этой инструкции: GitHub - Anonym-tsk/nfqws-keenetic

Удалось наконец-то “настроить”: Gemini 2.5 Pro Exp постарался. Ставлю на то что решение переусложнено, но уж поделюсь тем что есть, решение рабочее. Смысл как будто бы в том чтобы маркировать трафик, чтобы разделять его между sing-box и zapret.

/etc/sing-box/config
{
  "inbounds": [
    {
      "type": "tun",
      "tag": "all-in",
      "domain_strategy": "prefer_ipv4",
      "inet4_address": "172.16.0.1/30",
      "mtu": 9000,
      "stack": "system",
      "auto_route": false,
      "strict_route": false,
      "sniff": true,
      "sniff_override_destination": true
    },
    // ...
}

Здесь ключевое это auto_route, т.к. маршрутизацию мы будем настраивать вручную, автоматика не работает.

В /opt/zapret/config раскоментировал строки

INIT_FW_POST_UP_HOOK="/etc/firewall.zapret.hook.post_up"
INIT_FW_PRE_DOWN_HOOK="/etc/firewall.zapret.hook.pre_down"

И создал соответствующие файлы:

/etc/firewall.zapret.hook.post_up
#!/bin/sh

# --- Configuration Variables ---
SINGBOX_MARK="0x100"
SINGBOX_ROUTE_TABLE_ID="100" # Routing table ID for sing-box
SINGBOX_TUN_INTERFACE="tun0" # Your sing-box TUN interface name

NFT_ZAPRET_TABLE="inet zapret" # Standard zapret table name
NFT_PREROUTING_CHAIN="prerouting" # Standard prerouting chain in the zapret table
NFT_CUSTOM_MARK_CHAIN="singbox_fwmark_chain" # Custom chain for your marking rules
NFT_ZAPRET_IPSET_NAME="@zapret" # The nfset created by zapret for its IPs

# Define your VLESS server IP and Port if you want to exclude direct traffic to it from marking
# VLESS_SERVER_IP="your.vless.server.ip.address"
# VLESS_SERVER_PORT="your_vless_server_port"
# --- End Configuration Variables ---

# Function to log messages
log_message() {
    logger -t "custom_routing" "$1"
}

log_message "Starting custom routing rules UP script."

# 1. Set up nftables custom chain and marking rule
# -------------------------------------------------
# Check if the custom chain exists in the zapret table, create if not
if ! nft list chain "$NFT_ZAPRET_TABLE" "$NFT_CUSTOM_MARK_CHAIN" > /dev/null 2>&1; then
    log_message "Creating nft chain: $NFT_ZAPRET_TABLE $NFT_CUSTOM_MARK_CHAIN"
    nft add chain "$NFT_ZAPRET_TABLE" "$NFT_CUSTOM_MARK_CHAIN"
else
    log_message "Nft chain $NFT_ZAPRET_TABLE $NFT_CUSTOM_MARK_CHAIN already exists. Flushing."
    nft flush chain "$NFT_ZAPRET_TABLE" "$NFT_CUSTOM_MARK_CHAIN"
fi

# Check if a jump rule to the custom chain exists in prerouting, add if not
# The handle number (-a) allows for easier deletion if needed.
if ! nft -a list chain "$NFT_ZAPRET_TABLE" "$NFT_PREROUTING_CHAIN" | grep -q "jump $NFT_CUSTOM_MARK_CHAIN"; then
    log_message "Adding jump rule from $NFT_PREROUTING_CHAIN to $NFT_CUSTOM_MARK_CHAIN in table $NFT_ZAPRET_TABLE"
    # Add the jump at the beginning of the prerouting chain; adjust if needed
    nft insert rule "$NFT_ZAPRET_TABLE" "$NFT_PREROUTING_CHAIN" position 0 jump "$NFT_CUSTOM_MARK_CHAIN" comment "\"Jump to custom sing-box marking\""
else
    log_message "Jump rule to $NFT_CUSTOM_MARK_CHAIN already exists in $NFT_ZAPRET_TABLE $NFT_PREROUTING_CHAIN."
fi

# Add the marking rule to your custom chain
# This rule marks traffic NOT in zapret's IP set.
log_message "Adding marking rule to $NFT_CUSTOM_MARK_CHAIN: ip daddr != $NFT_ZAPRET_IPSET_NAME mark set $SINGBOX_MARK"
nft add rule "$NFT_ZAPRET_TABLE" "$NFT_CUSTOM_MARK_CHAIN" ip daddr != "$NFT_ZAPRET_IPSET_NAME" mark set "$SINGBOX_MARK" comment "\"Mark non-NFQWS traffic for sing-box\""

# Optional: If you want to exclude traffic directly to your VLESS server from being marked (and thus from sing-box loop)
# Ensure VLESS_SERVER_IP and VLESS_SERVER_PORT are defined above
# if [ -n "$VLESS_SERVER_IP" ] && [ -n "$VLESS_SERVER_PORT" ]; then
#    log_message "Adding rule to $NFT_CUSTOM_MARK_CHAIN to accept direct VLESS traffic without marking."
#    nft add rule "$NFT_ZAPRET_TABLE" "$NFT_CUSTOM_MARK_CHAIN" ip daddr "$VLESS_SERVER_IP" tcp dport "$VLESS_SERVER_PORT" accept comment "\"Allow direct VLESS server traffic\""
# fi


# 2. Set up iproute2 rules
# -------------------------
# Add rule to use the custom routing table for marked packets, if it doesn't exist
if ! ip rule show | grep -q "fwmark $SINGBOX_MARK lookup $SINGBOX_ROUTE_TABLE_ID"; then
    log_message "Adding ip rule: fwmark $SINGBOX_MARK lookup $SINGBOX_ROUTE_TABLE_ID"
    ip rule add fwmark "$SINGBOX_MARK" lookup "$SINGBOX_ROUTE_TABLE_ID" priority 20000
else
    log_message "IP rule for fwmark $SINGBOX_MARK lookup $SINGBOX_ROUTE_TABLE_ID already exists."
fi

# Add default route via sing-box TUN interface to the custom table, if it doesn't exist
# Ensure the sing-box TUN interface is up; this script runs after firewall, so it should be.
if ! ip route show table "$SINGBOX_ROUTE_TABLE_ID" | grep -q "default dev $SINGBOX_TUN_INTERFACE"; then
    log_message "Adding ip route: default dev $SINGBOX_TUN_INTERFACE table $SINGBOX_ROUTE_TABLE_ID"
    ip route add default dev "$SINGBOX_TUN_INTERFACE" table "$SINGBOX_ROUTE_TABLE_ID"
else
    log_message "IP route default dev $SINGBOX_TUN_INTERFACE table $SINGBOX_ROUTE_TABLE_ID already exists."
fi

log_message "Custom routing rules UP script finished."
exit 0
/etc/firewall.zapret.hook.pre_down
#!/bin/sh

# --- Configuration Variables ---
SINGBOX_MARK="0x100"
SINGBOX_ROUTE_TABLE_ID="100"
SINGBOX_TUN_INTERFACE="tun0" # Your sing-box TUN interface name

NFT_ZAPRET_TABLE_FAMILY_NAME="inet zapret" # Table family and name

# !! IMPORTANT: TRY THIS SET NAME FIRST, BASED ON THE NFTABLES ERROR HINT !!
# If this doesn't work, you will need to inspect 'nft list table inet zapret'
# after zapret starts to find the actual set name.
PROPOSED_NFT_SET_NAME_IN_TABLE="zapret"
# Original attempt (if the above fails, you might revert or find the correct one):
# PROPOSED_NFT_SET_NAME_IN_TABLE="zapret_ip"

NFT_ZAPRET_IPSET_REF="@${PROPOSED_NFT_SET_NAME_IN_TABLE}" # How the set is referenced in rules

NFT_PREROUTING_CHAIN_FQN="${NFT_ZAPRET_TABLE_FAMILY_NAME} prerouting"
NFT_CUSTOM_MARK_CHAIN_NAME="singbox_fwmark_chain"
NFT_CUSTOM_MARK_CHAIN_FQN="${NFT_ZAPRET_TABLE_FAMILY_NAME} ${NFT_CUSTOM_MARK_CHAIN_NAME}"
# --- End Configuration Variables ---

# Function to log messages
log_message() {
    logger -t "custom_routing_up" "$1"
}

log_message "Starting custom routing rules UP script."

# Keep a delay, as some initialization might still be asynchronous
log_message "Waiting 5 seconds for zapret to fully initialize its nftables structures..."
sleep 5

# 0. Verify that the main zapret table exists
# -------------------------------------------
if ! nft list table ${NFT_ZAPRET_TABLE_FAMILY_NAME} > /dev/null 2>&1; then
    log_message "CRITICAL ERROR: Zapret nftables table '${NFT_ZAPRET_TABLE_FAMILY_NAME}' does not exist. Cannot proceed."
    log_message "Please ensure zapret service has started correctly and created its main table."
    exit 1
fi
log_message "Zapret nftables table '${NFT_ZAPRET_TABLE_FAMILY_NAME}' confirmed to exist."

# 1. Verify that the PROPOSED IP set DEFINITION exists in the zapret table
# -----------------------------------------------------------------------
log_message "Attempting to verify existence of IP set definition '${PROPOSED_NFT_SET_NAME_IN_TABLE}' in table '${NFT_ZAPRET_TABLE_FAMILY_NAME}'..."
if ! nft list set ${NFT_ZAPRET_TABLE_FAMILY_NAME} "${PROPOSED_NFT_SET_NAME_IN_TABLE}" > /dev/null 2>&1; then
    log_message "CRITICAL ERROR: IP set definition '${PROPOSED_NFT_SET_NAME_IN_TABLE}' was NOT FOUND in table '${NFT_ZAPRET_TABLE_FAMILY_NAME}'."
    log_message "The rule referencing '${NFT_ZAPRET_IPSET_REF}' will fail."
    log_message "To resolve this: "
    log_message "  1. After 'service zapret restart' (even if this script fails), manually run: 'nft list table ${NFT_ZAPRET_TABLE_FAMILY_NAME}'"
    log_message "  2. Look for 'set <name> { ... }' definitions. Identify the correct set name that zapret uses for its IP lists."
    log_message "  3. Update the 'PROPOSED_NFT_SET_NAME_IN_TABLE' variable at the top of this script (${0}) with the correct name."
    log_message "Listing all currently defined sets in table '${NFT_ZAPRET_TABLE_FAMILY_NAME}' to help diagnosis:"
    nft list sets ${NFT_ZAPRET_TABLE_FAMILY_NAME} 2>&1 | logger -t "custom_routing_up" # Log the list of sets
    exit 1
fi
log_message "SUCCESS: IP set definition '${PROPOSED_NFT_SET_NAME_IN_TABLE}' (referenced as '${NFT_ZAPRET_IPSET_REF}') found in table '${NFT_ZAPRET_TABLE_FAMILY_NAME}'."


# 2. Set up nftables custom chain for marking rules
# -------------------------------------------------
# Check if the custom chain exists, create if not
if ! nft list chain ${NFT_CUSTOM_MARK_CHAIN_FQN} > /dev/null 2>&1; then
    log_message "Creating nft chain: ${NFT_CUSTOM_MARK_CHAIN_FQN}"
    nft add chain ${NFT_ZAPRET_TABLE_FAMILY_NAME} "${NFT_CUSTOM_MARK_CHAIN_NAME}"
else
    log_message "Nft chain ${NFT_CUSTOM_MARK_CHAIN_FQN} already exists. Flushing its rules."
    nft flush chain ${NFT_CUSTOM_MARK_CHAIN_FQN}
fi

# Add a jump rule from the main prerouting chain to your custom marking chain
# Ensure the prerouting chain exists in the zapret table (zapret itself should create this)
if ! nft -a list chain ${NFT_PREROUTING_CHAIN_FQN} | grep -q "jump ${NFT_CUSTOM_MARK_CHAIN_NAME}"; then
    log_message "Adding jump rule from ${NFT_PREROUTING_CHAIN_FQN} to ${NFT_CUSTOM_MARK_CHAIN_NAME}"
    # Insert at a high-priority position (e.g., 0 or adjust as needed)
    nft insert rule ${NFT_PREROUTING_CHAIN_FQN} position 0 jump "${NFT_CUSTOM_MARK_CHAIN_NAME}" comment "\"Jump to custom sing-box marking\""
else
    log_message "Jump rule to ${NFT_CUSTOM_MARK_CHAIN_NAME} already exists in ${NFT_PREROUTING_CHAIN_FQN}."
fi

# 3. Add the actual marking rule to your custom chain
# ---------------------------------------------------
log_message "Attempting to add marking rule to ${NFT_CUSTOM_MARK_CHAIN_FQN}: ip daddr != ${NFT_ZAPRET_IPSET_REF} mark set ${SINGBOX_MARK}"
if nft add rule ${NFT_CUSTOM_MARK_CHAIN_FQN} ip daddr != "${NFT_ZAPRET_IPSET_REF}" mark set "${SINGBOX_MARK}" comment "\"Mark non-NFQWS traffic for sing-box\""; then
    log_message "SUCCESS: Marking rule added to ${NFT_CUSTOM_MARK_CHAIN_FQN}."
else
    MARKING_RULE_ERROR_CODE=$?
    log_message "ERROR: Failed to add marking rule to ${NFT_CUSTOM_MARK_CHAIN_FQN}. Exit code: ${MARKING_RULE_ERROR_CODE}"
    log_message "This can happen if the IP set '${NFT_ZAPRET_IPSET_REF}' was not correctly identified/resolved or if there is a syntax error."
    log_message "Dumping current ruleset for table ${NFT_ZAPRET_TABLE_FAMILY_NAME} for review:"
    nft list table ${NFT_ZAPRET_TABLE_FAMILY_NAME} 2>&1 | logger -t "custom_routing_up"
    exit 1 # Exit if this crucial rule fails
fi


# 4. Set up iproute2 rules for sing-box
# -------------------------------------
log_message "Setting up iproute2 rules..."
# Add rule to use the custom routing table for marked packets
if ! ip rule show | grep -q "fwmark ${SINGBOX_MARK} lookup ${SINGBOX_ROUTE_TABLE_ID}"; then
    log_message "Adding ip rule: fwmark ${SINGBOX_MARK} lookup ${SINGBOX_ROUTE_TABLE_ID} priority 20000"
    ip rule add fwmark "${SINGBOX_MARK}" lookup "${SINGBOX_ROUTE_TABLE_ID}" priority 20000
else
    log_message "IP rule for fwmark ${SINGBOX_MARK} lookup ${SINGBOX_ROUTE_TABLE_ID} already exists."
fi

# Add default route via sing-box TUN interface to the custom table
if ! ip route show table "${SINGBOX_ROUTE_TABLE_ID}" | grep -q "default dev ${SINGBOX_TUN_INTERFACE}"; then
    log_message "Adding ip route: default dev ${SINGBOX_TUN_INTERFACE} table ${SINGBOX_ROUTE_TABLE_ID}"
    ip route add default dev "${SINGBOX_TUN_INTERFACE}" table "${SINGBOX_ROUTE_TABLE_ID}"
else
    log_message "IP route default dev ${SINGBOX_TUN_INTERFACE} table ${SINGBOX_ROUTE_TABLE_ID} already exists."
fi

log_message "Custom routing rules UP script finished successfully."
exit 0

имею ванильный xray-core + tun2socks для маршрута ipset листа и запрет, работает. настраивается не сложно, я тупил только над листом ipset