Raspberry Pi + xray между роутером и телевизором

Без этой секции пробовали?

Мне она всё сильно портила почему-то. Пока не прописал тег google в качестве final, ничего не работало.

P. S. Надо все-таки сесть и разобраться с dns запросами в sing-box, не понимаю, как там разные секции друг с другом связаны.

Вообще я понял, что если я хочу всегда использовать DoH гугла (а я хочу), то конфиг можно упростить до такого состояния:

{
	"log": {
		"disabled": true,
		"level": "info",
		"output": "/tmp/sing-box.log",
		"timestamp": true
	},
	"dns": {
		"servers": [
			{
				"tag": "google",
				"address": "https://8.8.8.8/dns-query"
			},
			{
			"tag": "block",
			"address": "rcode://success"
			}
		],
		"final": "google",
		"strategy": "ipv4_only"
	},
	"inbounds": [
		{
			"type": "tun",
			"interface_name": "tun0",
			"domain_strategy": "ipv4_only",
			"inet4_address": "172.16.250.1/30",
			"mtu": 1500,
			"auto_route": true,
			"strict_route": false,
			"sniff": true,
			"sniff_override_destination": true
		},
		{
			"type": "redirect",
			"tag": "redirect-in",
			"listen": "0.0.0.0",
			"listen_port": 100,
			"domain_strategy": "ipv4_only",
			"sniff": true,
			"sniff_override_destination": true
		}
	],
	"outbounds": [
		{
			# vless-out
		},
		{
			"type": "direct",
			"tag": "direct"
		},
		{
			"type": "block",
			"tag": "block"
		}
	],
	"route": {
		"rules": [
			{
                "ip_is_private": true,
                "outbound": "direct"
            },
			{
                "rule_set": [
					"geoip-ru",
					"geosite-ru"
				],
                "outbound": "direct"
            }
        ],
	"rule_set": [
		{
			"type": "remote",
			"tag": "geoip-ru",
			"format": "binary",
			"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ru.srs"
		},
		{
			"type": "remote",
			"tag": "geosite-ru",
			"format": "binary",
			"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ru.srs"
		}
	],
	"final": "vless-out",
	"auto_detect_interface": true
	}
}

Примерное понимание работы DNS и правил дает вот этот пример: Sing-Box DNS - VPN Router for Home Networks

Я, правда, так до конца и не уверен, какие ответы от 8.8.8.8 на 53 порту в итоге отображает tcpdump. Если я корректно понимаю, телевизор пытается обращаться к 8.8.8.8 (установлен в настройках тв), sing-box перенаправляет запрос согласно правилам, а потом как бы возвращает ответ от 8.8.8.8. Но это если я правильно понял.

Выкроил еще немного времени и протестировал кучу разных конфигов, в том числе те, которые у меня не работали. Тестировал с полной перезагрузкой телевизора с выключением из розетки на 30+ секунд. (Всем советую делать так же.)

Во-первых, как я и предположил выше, UDP на 443 порту можно не блокировать (хотя возможно зависит от модельного года, но у меня QUIC не используется).

Во-вторых, за эти дни что-то поменялось. Все конфиги с redirect+tun, которые у меня не работали по причинам, теперь работают. С локальными DNS, с удаленными DNS, с роутингом по geoip и geosite, с роутингом по файлику youtube.json - работают, и видео открываются, и поиск, и рекомендации, все как рукой сняло. Иногда проявляется странный нюанс: в тех конфигах, что раньше не работали, приложение youtube временами открывается со второго раза. В первый раз начинает загружаться, вылетает, и телевизор выдает сообщение “Произошла ошибка сети”. Снова открываешь youtube - работает. Но ошибка воспроизводится как-то нестабильно.

В итоге оставил так:

{
	"log": {
		"disabled": true,
		"level": "info",
		"output": "/tmp/sing-box.log",
		"timestamp": true
	},
	"dns": {
		"servers": [
			{
				"tag": "google",
				"address": "https://8.8.8.8/dns-query"
			},
			{
			"tag": "block",
			"address": "rcode://success"
			}
		],
		"final": "google",
		"strategy": "ipv4_only"
	},
	"inbounds": [
		{
			"type": "tun",
			"interface_name": "tun0",
			"domain_strategy": "ipv4_only",
			"inet4_address": "172.16.250.1/30",
			"mtu": 1500,
			"auto_route": true,
			"strict_route": false,
			"sniff": true,
			"sniff_override_destination": true
		},
		{
			"type": "redirect",
			"tag": "redirect-in",
			"listen": "0.0.0.0",
			"listen_port": 100,
			"domain_strategy": "ipv4_only",
			"sniff": true,
			"sniff_override_destination": true
		}
	],
	"outbounds": [
		{
			"type": "vless",
			"tag": "vless-out",
			"server": "IP_ADDR_HERE",
			"server_port": 443,
			"uuid": "UUID_HERE",
			"flow": "xtls-rprx-vision",
			"network": "tcp",
			"tls": {
				"enabled": true,
				"server_name": "DOMAIN_HERE",
				"alpn": ["h2"],
				"utls": {
					"enabled": true,
					"fingerprint": "chrome"
				},
				"reality": {
					"enabled": true,
					"public_key": "PUBLIC_KEY_HERE",
					"short_id": "SHORT_ID_HERE"
				}
			},
			"packet_encoding": "xudp"
		},
		{
			"type": "direct",
			"tag": "direct"
		},
		{
			"type": "block",
			"tag": "block"
		}
	],
	"route": {
		"rules": [
			{
                "ip_is_private": true,
                "outbound": "direct"
            },
			{
                "rule_set": [
					"geoip-ru",
					"geosite-ru"
				],
                "outbound": "direct"
            }
        ],
	"rule_set": [
		{
			"type": "remote",
			"tag": "geoip-ru",
			"format": "binary",
			"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ru.srs"
		},
		{
			"type": "remote",
			"tag": "geosite-ru",
			"format": "binary",
			"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ru.srs"
		}
	],
	"final": "vless-out",
	"auto_detect_interface": true
	}
}

Также если кому-то интересно, при роутинге по youtube.json сам файлик выглядел так:

{
    "version": 1,
    "rules": [
        {
            "domain_suffix": [
				".googlevideo.com",
				".youtube.com",
				".ggpht.com",
				".ytimg.com",
				".googleapis.com",
				".youtu.be",
				".google.com",
				".yt.be",
				".youtubekids.com",
				".nhacmp3youtube.com",
				".googleusercontent.com",
				".gstatic.com"
            ]
        }
    ]
}

Еще пара мыслей для тех, у кого все работало, а потом перестало.

  1. Отключить ipv6, если не используете. Поначалу отключал так:
sudo nano /etc/sysctl.conf
Добавить в конец:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv4.ip_forward=1
и выполнить:
sudo sysctl -p

Оно даже срабатывает, но после перезагрузки ipconfig почему-то снова показывает ipv6 адрес, что может создавать неприятности. Есть статья по RPI, в которой написано, как надо:

sudo nano /boot/firmware/cmdline.txt
Добавить в конец строки: ipv6.disable=1

После этого сделать sudo reboot и выполнить ifconfig, чтобы убедиться, что ipv6 для eth0 больше не отображается.

  1. Не забывать сохранять правила iptables, чтобы не слетали после перезагрузки.
sudo iptables -t nat -A PREROUTING -s IP_ТЕЛЕВИЗОРА/32 -p tcp -j REDIRECT --to-port 100
sudo apt-get install iptables-persistent
sudo netfilter-persistent save
sudo systemctl enable netfilter-persistent
sudo systemctl start netfilter-persistent
  1. sing-box после перезагрузки может стартовать слишком рано. Если после ребута все перестало работать, проверить статус сервиса sing-box. Если он failed, хотя раньше запускался, то надо поправить конфиг sing-box.service примерно так (главным образом строку After):
[Unit]
Description=Sing-Box Service
After=network.target nss-lookup.target network-online.target

[Service]
ExecStart=/usr/local/bin/sing-box run -c /etc/sing-box/config.json
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Хотя по идее в репах должен быть правильный конфиг, но мало ли.

Поделюсь своим опытом настройки.

Дано

  • SmartTV Samsung c TizenOS (т.е. не умеет в прокси и VPN),
  • роутер с OpenWRT 23.05.0 с современным железом (поддержка WiFi 5 и выше),
  • VPS на территории потанцевального противника

Задача

Реализация желания смотреть Ютуб, злоумышленно эксплуатируя уязвимость в виде статьи 29 Конституции РФ.

Решение

sing-box на роутере + пара правил nftables на нём же.

Подробности:

Ставим sing-box

opkg update && opkg install sing-box

Конфиг sing-box

cat /etc/config/sing-box

config sing-box 'main'
        option enabled '1'
        option user 'sing-box'
        option conffile '/etc/sing-box/config.json'
        option workdir '/usr/share/sing-box'

cat /etc/sing-box/config.json

{
  "log": {
    "level": "warn",
    "disabled": false
  },
  "dns": {
    "servers": [
      {
        "tag": "dns-google",
        "address": "tls://8.8.8.8",
        "detour": "vless-xtls-vision-out"
      },
      { //  в моём случае "address" не "local", т.к. на роутере крутится DNS-сервер для WAN и LAN сетей
        //  и настроены разные view для локальных и внешних клиентов.
        //  На интерфейсе 192.168.1.1 я гарантированно попадаю на view LAN и мой домен резолвится в его локальный IP, а не в белый.
        "tag": "dns-local",
        "address": "192.168.1.1", 
        "detour": "direct-out"
      }
    ], //servers
    "rules": [
      {
        "outbound": [
          "direct-out"
        ],
        "server": "dns-local"
      },
      {
        "domain_suffix": [
          "local",
          "home",
          "isp.local.site"
        ],
        "server": "dns-local"
      },
      {
        "outbound": [
          "vless-xtls-vision-out"
        ],
        "server": "dns-google"
      }
    ]
  }, // dns
  "inbounds": [
    {
     "type": "redirect",
     "tag": "redirect-in",
     "listen": "192.168.1.1",
     "listen_port": 3129,
     "sniff": true,
     "tcp_fast_open": true
    },
    {
      "listen": "192.168.1.1",
      "listen_port": 3130,
      "tag": "vless-xtls-vision-mixed-in",
      "type": "mixed"
    }
  ],
  "outbounds": [
    {
      "flow": "xtls-rprx-vision",
      "packet_encoding": "xudp",
      "server": "REDACTED",
      "server_port": 443,
      "tag": "vless-xtls-vision-out",
      "tls": {
        "enabled": true,
        "server_name": "REDACTED",
        "utls": {
          "enabled": true,
          "fingerprint": "chrome"
        }
      },
      "type": "vless",
      "uuid": "REDACTED"
    },
    {
      "type": "direct",
      "tag": "direct-out"
    },
    {
      "type": "dns",
      "tag": "dns-out"
    }
  ],
  "route": {
    "rules": [
      { // rule for connecting without proxy
        "domain_suffix": [
          "local",
          "home",
          "isp.local.site"
          ],
         "outbound": "direct-out"
      },
        { // Youtube for Samsung Smart TV
          "inbound": "redirect-in",
          "domain_suffix": [
            "youtube.com",
            "youtu.be",
            "googlevideo.com",
            "ytimg.com"
          ],
          "outbound": "vless-xtls-vision-out"
        },
       {
         "rule_set": "antizapret",
         "outbound": "vless-xtls-vision-out"
       },
       {
         "protocol": "dns",
         "outbound": "dns-out"
        },
        {
          "inbound": "redirect-in",
          "outbound": "direct-out"
        },
        {
          "inbound": "vless-xtls-mixed-in",
          "outbound": "vless-xtls-vision-out"
        }
    ], //rules
    "rule_set": [
      { // Remote rule-set will be cached if experimental.cache_file.enabled.
        "tag": "antizapret",
        "type": "remote",
        "format": "binary",
        "url": "https://github.com/savely-krasovsky/antizapret-sing-box/releases/latest/download/antizapret.srs",
        "download_detour": "vless-xtls-vision-out",
        "update_interval": "1d"
      }
    ]
  }
}

Правила nftables

В формате UCI:

/etc/config/firewall

config rule
        option enabled          1
        option name             SamsungTV-block-quick
        option target           REJECT
        option src              lan
        option dest             wan
        option proto            udp
        option src_ip           'SMART.TV.IP.ADDRESS'
        option dest_port        443
        option family           ipv4

config redirect
        option enabled          1
        option name             SamsungTV-redirect-HTTPS-to-sing-box
        option target           DNAT
        option src              lan
        option dest             wan
        option proto            tcp
        option src_ip           'SMART.TV.IP.ADDRESS'
        option src_dport        443
        option dest_ip          '192.168.1.1'
        option dest_port        3129 # port of 'redirect-in' sing-box inbound
        option family           ipv4

В виде правил nftables:

fw4 print:

        chain forward_lan {
                ip saddr SMART.TV.IP.ADDRESS udp dport 443 counter jump reject_to_wan comment "!fw4: DNAT-SamsungTV-block-quick"
        }
        chain dstnat_lan {
                ip saddr SMART.TV.IP.ADDRESS tcp dport 443 counter dnat 192.168.1.1:3129 comment "!fw4: DNAT-SamsungTV-redirect-HTTPS-to-sing-box"
        }

В данном случае блочим QUICK (UDP/443) и редиректим только HTTPS (TCP/443). Можно редиректить и весь трафик Smart TV, но у меня и так прекрасно работает.

Crontab

crontab -e

# restart sing-box every 2 hours to avoid high memory consumption and OOM-killer spawning
0  */2 * * * /etc/init.d/sing-box restart

Итого

Редиректим HTTPS со смартТВ на “redirect”-вход sing-box и ходим на домены, имеющие отношение к YouTube, через прокси.
Также имеем HTTP/SOCKS прокси.
Маршрутизация несложная: сначала на локальные домены и на сайт провайдера лезем напрямую, затем правило для SmartTV, которое редиректит трафик Ютуба в прокси.
Далее бонусом идёт маршрутизация на основе правил Antizapret в новом формате (SRS).
Ещё далее - это скорее fallbacks, до них дело вряд ли дойдёт.

Каждые два часа рестартим sing-box во избежание прихода OOM-killer.
Тут нужно заметить, что sing-box (как и xray) просто не запускается, если ограничить виртуальную память процесса до менее чем 800 МБ. На роутере, как вы понимаете, пока таких объёмов RAM нет. По умолчанию sing-box заявляет себе 1.2 ГБ виртуальной памяти а мы надеемся что ему не понадобится больше, чем у нас есть (у меня на роутере 256 МБ RAM). Поэтому на всякий случай рестартим sing-box каждые 2 часа. На современном роутере (WiFi 5+) это происходит моментально. Почему эти программы требуют (не потребляют!) так много виртуальной памяти - я не погромист, поэтому не знаю, в коде гошечки не колупался.

Бегло протестировал - Ютуб на ТВ заработал как встарь. Пользуйтесь, друзья!
@ValdikSS , привет тебе из Гудлайновской сети!

Спустя некоторое время эксплуатации sing-box на Openwrt пришлось немного доработать напильником, чтобы это всё работало стабильно. Нестабильно это всё работало потому, что телевизор - не единственный клиент для sing-box на роутере, и коннектов довольно приличное количество круглосуточно.

Доработки:

  1. Блокировка IPv6-коннектов в sing-box. Если IPv6 нет, то делать это обязательно, т.к. неотключение обязательно влёчёт за собой наличие большого количества соединений в таймауте, и они забивают таблицу nf_conntrack. Просто потому, что по умолчанию net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60.

  2. Увеличение количества открытых файлов для процесса sing-box

  3. В /etc/sing-box/config.json в секцию "dns" добавить опцию "strategy": "ipv4_only":

  "dns": {
    "strategy": "ipv4_only",
    "servers": [
      {
        "tag": "dns-google",
        ...
  1. в /etc/init.d/sing-box после строк
        procd_open_instance "$NAME.main"
        procd_set_param command "$PROG" run -c "$conffile" -D "$workdir"

добавить

        procd_set_param limits nofile="65536 65536"

ВАЖНО: Любое обновление пакета sing-box перезапишет файл /etc/init.d/sing-box на тот, который идёт с пакетом. Поэтому после каждого обновления sing-box нужно заново прописывать лимит.

  1. в /etc/sysupgrade.conf добавить строку
    /etc/init.d/sing-box, если сохраняете конфиги.

Без этих мер sing-box работал нестабильно и падал. Или не падал, но упирался в лимиты, спамил ошибками в лог и для меня как пользователя сервиса фактически сервис не предоставлялся. Сейчас sing-box не падает, в лимиты не упирается, спама ошибок нет, сервис работает вторые сутки стабильно.

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

Хочу настроить sing-box, чтобы только определенные подсети и домены шли к VPS-серверу, а все остальное шло через bypass. После тестирования своего конфига было вроде все ок, но имеется проблема с udp трафиком.
Untitled Diagram.drawio

На прокси сервере в локалке два правила для fw:

iptables -t nat -A PREROUTING -s 192.168.13.0/25 -p udp -j REDIRECT --to-port 100
iptables -t nat -A PREROUTING -s 192.168.13.0/25 -p tcp -j REDIRECT --to-port 100

Сам конфиг sing-box выглядит следующим образом:

{
  "dns": {
    "independent_cache": true,
    "rules": [
      {
        "domain": [
          "2ip.ru",
          "youtube.com",
          "googlevideo.com",
          "ytimg.com",
          "youtu.be",
          "ggpht.com",
          "youtubei.googleapis.com",
          "yt4.ggpht.com",
          "ytimg.l.google.com",
          "nhacmp3youtube.com",
          "googleusercontent.com",
          "googleapis.com",
          "gstatic.com"
        ],
        "domain_keyword": [],
        "domain_regex": [],
        "domain_suffix": [],
        "geosite": [],
        "server": "dns-remote"
      },
      {
        "query_type": [
          32,
          33
        ],
        "server": "dns-block"
      },
      {
        "domain_suffix": ".lan",
        "server": "dns-block"
      }
    ],
    "servers": [
      {
        "address": "https://1.1.1.1/dns-query",
        "address_resolver": "dns-local",
        "detour": "proxy",
        "strategy": "prefer_ipv4",
        "tag": "dns-remote"
      },
      {
        "address": "local",
        "address_resolver": "dns-local",
        "detour": "direct",
        "strategy": "prefer_ipv4",
        "tag": "dns-direct"
      },
      {
        "address": "rcode://success",
        "tag": "dns-block"
      },
      {
        "address": "local",
        "detour": "direct",
        "tag": "dns-local"
      }
    ]
  },
   "inbounds": [{
      "type": "redirect",
      "tag": "redirect-in",
      "sniff": true,
      "listen": "0.0.0.0",
      "listen_port": 100
  }],
  "log": {
    "level": "info"
  },
  "outbounds": [
    {
      "domain_strategy": "",
      "flow": "xtls-rprx-vision",
      "packet_encoding": "",
      "server": "111.111.111.111",
      "server_port": 44444,
      "tag": "proxy",
      "tls": {
        "enabled": true,
        "reality": {
          "enabled": true,
          "public_key": "wwwwwwwwwwwwwwwwwwwwwwwwwww",
          "short_id": "ccccccccc"
        },
        "server_name": "google.com",
        "utls": {
          "enabled": true,
          "fingerprint": "safari"
        }
      },
      "type": "vless",
      "uuid": "66666666666666666666"
    },
    {
      "tag": "direct",
      "type": "direct"
    },
    {
      "tag": "bypass",
      "type": "direct"
    },
    {
      "tag": "block",
      "type": "block"
    },
    {
      "tag": "dns-out",
      "type": "dns"
    }
  ],
  "route": {
    "final": "bypass",
    "rules": [
      {
        "outbound": "dns-out",
        "protocol": "dns"
      },
      {
        "domain": [],
        "domain_keyword": [],
        "domain_regex": [],
        "domain_suffix": [],
        "geosite": [
          "category-ads-all"
        ],
        "outbound": "block"
      },
      {
        "domain": [
          "2ip.ru",
          "youtube.com",
          "googlevideo.com",
          "ytimg.com",
          "youtu.be",
          "ggpht.com",
          "youtubei.googleapis.com",
          "yt4.ggpht.com",
          "ytimg.l.google.com",
          "nhacmp3youtube.com",
          "googleusercontent.com",
          "googleapis.com",
          "gstatic.com"
        ],
        "domain_keyword": [],
        "domain_regex": [],
        "domain_suffix": [],
        "geosite": [],
        "outbound": "proxy"
      },
      {
        "geoip": [],
        "ip_cidr": [
          "147.75.208.0/20",
          "185.89.216.0/22",
          "31.13.24.0/21",
          "31.13.64.0/19",
          "31.13.96.0/19",
          "45.64.40.0/22",
          "66.220.144.0/20",
          "69.63.176.0/20",
          "69.171.224.0/19",
          "74.119.76.0/22",
          "102.132.96.0/20",
          "103.4.96.0/22",
          "129.134.0.0/16",
          "157.240.0.0/16",
          "173.252.64.0/18",
          "179.60.192.0/22",
          "185.60.216.0/22",
          "204.15.20.0/22",
          "102.221.188.0/22",
          "163.114.128.0/20",
          "164.163.191.64/26",
          "199.201.64.0/22",
          "64.233.160.0/19",
          "66.102.0.0/20",
          "66.249.64.0/18",
          "72.14.192.0/18",
          "74.125.0.0/16",
          "209.85.128.0/17",
          "216.239.32.0/19",
          "64.18.0.0/20",
          "108.177.8.0/21",
          "172.217.0.0/19",
          "173.194.0.0/16",
          "207.126.144.0/20",
          "216.58.192.0/19",
          "3.2.40.0/25",
          "3.4.3.0/24",
          "3.4.4.0/24",
          "3.4.6.0/24",
          "3.5.76.0/22",
          "3.5.80.0/21",
          "13.34.14.128/26",
          "13.34.23.96/27",
          "13.34.23.128/25",
          "13.34.24.96/27",
          "13.34.24.128/26",
          "13.34.24.192/27",
          "13.34.25.64/26",
          "13.34.25.128/26",
          "13.34.26.0/25",
          "13.34.28.0/24",
          "13.34.42.128/26",
          "13.34.54.96/27",
          "13.34.54.128/25",
          "13.34.55.0/27",
          "13.34.57.0/26",
          "13.34.61.192/26",
          "13.34.67.64/26",
          "13.34.67.128/25",
          "13.34.68.0/26",
          "13.34.70.64/26",
          "13.34.79.64/26",
          "13.34.82.64/26",
          "13.34.82.128/25",
          "13.34.85.64/26",
          "13.34.86.192/26",
          "13.34.88.192/26",
          "13.34.93.128/26",
          "13.248.112.0/24",
          "15.177.80.0/24",
          "15.181.0.0/19",
          "15.181.64.0/20",
          "15.181.116.0/22",
          "15.181.128.0/20",
          "15.181.245.0/24",
          "15.181.248.0/24",
          "15.181.250.0/23",
          "15.181.252.0/23",
          "15.193.7.0/24",
          "15.220.0.0/19",
          "15.220.32.0/21",
          "15.220.40.0/22",
          "15.220.200.0/21",
          "15.220.208.128/26",
          "15.220.224.0/23",
          "15.220.226.0/24",
          "15.220.252.0/22",
          "15.221.1.0/24",
          "15.221.7.0/24",
          "15.221.148.0/23",
          "15.230.67.192/26",
          "15.230.68.0/25",
          "15.230.92.0/24",
          "15.230.240.0/24",
          "15.230.247.0/24",
          "15.248.40.0/22",
          "15.248.80.0/20",
          "15.253.0.0/16",
          "15.254.0.0/16",
          "18.34.48.0/20",
          "18.34.244.0/22",
          "18.88.128.0/18",
          "18.236.0.0/15",
          "18.246.0.0/16",
          "34.208.0.0/12",
          "35.71.64.0/22",
          "35.80.0.0/12",
          "35.155.0.0/16",
          "35.160.0.0/13",
          "44.224.0.0/11",
          "50.112.0.0/16",
          "52.10.0.0/15",
          "52.12.0.0/15",
          "52.24.0.0/14",
          "52.32.0.0/13",
          "52.40.0.0/14",
          "52.46.180.0/22",
          "52.46.216.0/22",
          "52.46.249.0/24",
          "52.75.0.0/16",
          "52.88.0.0/15",
          "52.92.128.0/17",
          "52.93.12.12/31",
          "52.93.14.18/31",
          "52.93.20.0/24",
          "52.93.120.179/32",
          "52.93.122.218/32",
          "52.93.240.146/31",
          "52.93.240.148/31",
          "52.93.240.152/29",
          "52.93.240.160/27",
          "52.93.240.192/29",
          "52.93.240.200/30",
          "52.93.240.204/31",
          "52.94.10.0/24",
          "52.94.28.0/23",
          "52.94.76.0/22",
          "52.94.116.0/22",
          "52.94.120.0/22",
          "52.94.128.0/22",
          "52.94.176.0/20",
          "52.94.197.0/24",
          "52.94.208.0/21",
          "52.94.248.96/28",
          "52.94.249.64/28",
          "52.95.40.0/24",
          "52.95.230.0/24",
          "52.95.247.0/24",
          "52.95.255.112/28",
          "52.119.160.0/20",
          "52.119.252.0/22",
          "52.144.194.64/26",
          "52.144.194.128/26",
          "52.144.197.128/25",
          "52.218.128.0/17",
          "54.68.0.0/14",
          "54.148.0.0/15",
          "54.184.0.0/13",
          "54.200.0.0/14",
          "54.212.0.0/15",
          "54.214.0.0/16",
          "54.218.0.0/16",
          "54.239.0.32/28",
          "54.239.2.0/23",
          "54.239.48.0/22",
          "54.240.230.0/23",
          "54.240.248.0/21",
          "54.244.0.0/15",
          "64.252.65.0/24",
          "64.252.70.0/23",
          "64.252.72.0/23",
          "70.224.192.0/18",
          "99.77.130.0/24",
          "99.77.152.0/24",
          "99.77.186.0/24",
          "99.77.232.0/24",
          "99.77.253.0/24",
          "99.78.196.0/22",
          "99.150.56.0/21",
          "99.151.186.0/23",
          "100.20.0.0/14",
          "108.166.224.0/21",
          "108.166.240.0/21",
          "142.4.160.16/29",
          "142.4.160.32/29",
          "142.4.160.56/29",
          "142.4.160.64/29",
          "142.4.160.96/28",
          "142.4.160.224/29",
          "150.222.15.132/31",
          "150.222.74.0/23",
          "150.222.102.0/24",
          "150.222.176.0/22",
          "150.222.180.0/24",
          "150.222.196.0/24",
          "150.222.214.0/24",
          "151.148.33.0/24",
          "162.222.148.0/22",
          "176.32.125.0/25",
          "176.32.125.128/26",
          "184.32.0.0/12",
          "205.251.232.0/22"
        ],
        "outbound": "proxy"
      },
      {
        "network": "udp",
        "outbound": "block",
        "port": [
          135,
          137,
          138,
          139,
          5353
        ]
      },
      {
        "ip_cidr": [
          "224.0.0.0/3",
          "ff00::/8"
        ],
        "outbound": "block"
      },
      {
        "outbound": "block",
        "source_ip_cidr": [
          "224.0.0.0/3",
          "ff00::/8"
        ]
      }
    ]
  }
}

Что нужно добавить или убавить чтобы с udp все было ок?

для udp надо использовать tproxy или tun. ну и правила у вас полная жесть

Парни! Не понимаю почему вы sing-box мучаете? Имея Малинку можно домашний трафик не заворачивать в WG чтобы толкнуть его VLESS-ом (или shadowsocks) на свой VPS. Устанавливайте 3X-ui на VPS и Малину! В панели 3X-ui Малины создайте HTTP-прокси (для домашних - без пароля), и делайте бридж на внешний VPS. В маршрутизации задаете правило и всё прекрасно работает. Настраивать довольно легко.
Дам пример, где человек тоже делает через WG. Можно пойти по его инструкциям, но… ещё раз - (наверное) достаточно HTTP-прокси для дома. В браузерах или в системе, указываете, что ходить в Интет нужно через прокси и… радуетесь! Если кто-то домашним биторентом пользуется, то можно добавить правило обхода в маршрутизации - direct - IP и порт, и поднять это правило над созданным Inboud.
Не реклама: “Настройка двойного VPN через 3X-UI

Если честно, по приведенной ссылке на скринах вообще ничего не видно. Поэтому я не совсем понял, в чем плюс конкретно 3x-ui, у которого под капотом все тот же xray (который делает то же что и sing-box).

В браузерах или в системе, указываете, что ходить в Интет нужно через прокси

Так а если в браузерах или системе нельзя указать, что нужно через прокси ходить?

Когда начнете панель настраивать, то всё будет видно: смотрите на свой экран и на картинку - мелкие детали ничего не дают. Да и другого примера я не видел нигде, а так бы поделился.
А на счет прокси… ну, может, на ТВ может и не быть, конечно. А так… про всё браузеры не знаю, а системы трудно представить без настроек прокси. Может пример приведете?
Обратите внимание на Slimjet - очень удобен для работы с прокси - позволяет использовать несколько профилей и быстро их переключать.

Так я не спорю, по возможности всё надо делать наиболее простым способом. В теме как раз обсуждается проблема, при которой явное проксирование не вариант, потому что таких настроек на клиенте попросту нет.

Друзья, помогите разобраться, что не так с настройкой sing-box.

Дано:

  1. виртуалка с Ubuntu (192.168.1.148), на ней настроен sing-box, все работает. Используется как маршрутизатор.
    Домашний роутер перенаправляет на нее трафик, адресованный определенным ip-адресам и подсетям. Оттуда через sing-box в VPS.

  2. вторая такая же виртуала с Ubuntu (192.168.1.75).
    Настроена с нуля, точно так же, такой же конфиг sing-box, заменен только ip-адрес в конфиге.

На второй никак не работает. Сам sing-box работает, в логах чисто, но через curl ничего не отдает, виснет:

curl --interface tun0 https://2ip.io
curl: (28) Failed to connect to 2ip.io port 443 after 136133 ms: Couldn't connect to server

Мне кажется, что с маршрутизацией на уровне ОС.
Сравнил две машины, нашел только одно различие в выводе “ip route”

На машине 192.168.1.148, где все работает:

# ip route show
default via 192.168.1.1 dev enp1s0 proto dhcp src 192.168.1.148 metric 100
169.254.0.0/16 dev enp1s0 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
192.168.1.0/24 dev enp1s0 proto kernel scope link src 192.168.1.148 metric 100
192.168.1.148/30 dev tun0 proto kernel scope link src 192.168.1.148

На машине 192.168.1.75, где не работает:

# ip route
default via 192.168.1.1 dev enp1s0 proto dhcp src 192.168.1.75 metric 100
192.168.1.0/24 dev enp1s0 proto kernel scope link src 192.168.1.75 metric 100
192.168.1.1 dev enp1s0 proto dhcp scope link src 192.168.1.75 metric 100
192.168.1.72/30 dev tun0 proto kernel scope link src 192.168.1.75

В последней строке маршрут, который добавляет сам sing-box. В первом случае он делает маршрут для адреса 192.168.1.148/30, где 192.168.1.148 - это и есть адрес виртуалки.

Во втором случае он делает маршрут для адреса 192.168.1.72/30, при этом адрес виртуалки другой - 192.168.1.75

Возможно, причина в этом? Но этот маршрут вручную поправить не могу. Удалить дает, а создать маршрут для “192.168.1.75/30” не дает, т.к. это некорректный адрес подсети.

На всякий случай прикладываю конфиг:

{
  "log": {
    "disabled": false,
    "level": "debug",
    "output": "/tmp/sing-box.log",
    "timestamp": true
  },
  "dns": {
    "servers": [
      {
        "address": "tls://8.8.8.8"
      }
    ]
  },
  "inbounds": [
    {
      "type": "tun",
      "interface_name": "tun0",
      "domain_strategy": "ipv4_only",
      "inet4_address": "192.168.1.75/30",
      "auto_route": true,
      "strict_route": false,
      "sniff": true
   }
  ],
	"outbounds": [
		{
			"type": "vless",
			"server": "xxx",
			"server_port": 443,
			"uuid": "xxx",
			"flow": "xtls-rprx-vision",
			"tls": {
				"enabled": true,
				"insecure": false,
				"server_name": "microsoft.com",
				"utls": {
					"enabled": true,
					"fingerprint": "chrome"
				},
				"reality": {
					"enabled": true,
					"public_key": "xxx",
					"short_id": "xxx"
				}
			}
		},
		{
			"type": "direct",
			"tag": "direct"
		},
			{
			"type": "dns",
			"tag": "dns-out"
			}
	],
  "route": {
  	"rules": [
			{
				"protocol": "dns",
				"outbound": "dns-out"
			}
			],

    "auto_detect_interface": true
  }
}

с постером выше решили в лс: inet4_address tun никак не связан с локальным ip устройства, там должно быть 172.19.0.1/30

Да, все заработало. Огромное спасибо за помощь!

если использовать tun, то что еще необходимо настроить помимо net.ipv4.conf.all.forwarding=1 ?

конфигурация inbound следующая:

  {
         "auto_route":true,
         "domain_strategy":"prefer_ipv4",
         "endpoint_independent_nat":true,
         "inet4_address":"172.19.0.1/28",
         "interface_name":"nekoray-tun",
         "mtu":1500,
         "sniff":true,
         "sniff_override_destination":false,
         "stack":"gvisor",
         "strict_route":false,
         "tag":"tun-in",
         "type":"tun"
      }

Всем привет! Слежу за этой темой самого его появления и собсна по ней же и настраивал малинку для всех устройств дома. Схема такая: Клиент - микротик - малина - микротик - мир. На днях обнаружил, что перестали скачиваться приложения с Google Play. Перепробовал уже кучу доменов исключать/добавлять. Не помогает. Есть у кого-нибудь соображения по этому поводу?

Сейчас использую следующие домены:

root@raspberrypi:/etc/sing-box# cat ./rule-set/youtube.json
{
    "version": 1,
    "rules": [
        {
            "domain_suffix": [
                                ".googlevideo.com",
                                ".youtube.com",
                                ".ggpht.com",
                                ".ytimg.com",
                                ".googleapis.com",
                                ".youtu.be",
                                ".google.com",
                                ".yt.be",
                                ".youtubekids.com",
                                ".nhacmp3youtube.com",
                                ".googleusercontent.com",
                                ".gstatic.com"
            ]
        }
    ]
}
root@raspberrypi:/etc/sing-box# cat ./rule-set/discord.json
{
    "version": 1,
    "rules": [
        {
            "domain_suffix": [
                                                        "dis.gd",
                                                        "discord-activities.com",
                                                        "discord.co",
                                                        "discord.com",
                                                        "discord.design",
                                                        "discord.dev",
                                                        "discord.gg",
                                                        "discord.gift",
                                                        "discord.gifts",
                                                        "discord.media",
                                                        "discord.new",
                                                        "discord.store",
                                                        "discord.tools",
                                                        "discordactivities.com",
                                                        "discordapp.com",
                                                        "discordapp.net",
                                                        "discordcdn.com",
                                                        "discordmerch.com",
                                                        "discordpartygames.com",
                                                        "discordsays.com",
                                                        "discordsez.com",
                                                        "discordstatus.com"
            ]
        }
    ]
}

пользуйтесь wireshark/tcpdump и смотрите на какие домены идут запросы

Проблема была конкретно в этом домене “.googleapis.com” . Пока без него ютюб работает нормально

Мое итоговое решение. Возможно кому пригодится. Ну и будет для меня шпаргалкой на всякий случай :slight_smile:
Железо:
Raspberri Pi 4b + Mikrotik rb2011uias-2hnd + keenetic voyager pro (в самой схеме она не участвует, просто жертва обстоятельств)
Задача:
В квартире есть тв, планшет, 2 телефона и 2 ПК (ну и горстка “умных устройств”, но их мы лешим просмотра ютюба и посиделок в дискорде с друзьями). Необходимо сделать так, чтоб на всем этом работал Ютюб, дискорд и прочие выкидыши капиталистического загнивающего запада. :upside_down_face:
Ну и желательно чтоб ничего на клиентах настраивать не пришлось. Мне лень.
Решение:
Арендуем VPS. На ней поднимаем панель 3x-ui. Это делается в 2 команды. на этом заострять внимания не будем.
На малинке:

  1. Разрешаем форвардинг пакетов и отключаем ipv6
sudo nano /etc/sysctl.conf
Добавить в конец:
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv4.ip_forward=1
и выполнить:
sudo sysctl -p
  1. Маскарадим буквально все, что через нас пролетает.
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Сохраняем правила:
sudo apt-get install iptables-persistent
sudo netfilter-persistent save
sudo systemctl enable netfilter-persistent
sudo systemctl start netfilter-persistent
  1. Качаем и настраиваем Sing-box
bash <(curl -fsSL https://sing-box.app/deb-install.sh)

Конфиг /etc/sing-box/config.json

{
"log": {
    "disabled": false,
    "level": "warn",
    "output": "/tmp/sing-box.log",
    "timestamp": true
    },
"dns": {
	"strategy": "ipv4_only",
	"servers": [
	{
	"tag": "google",
	"address": "https://8.8.8.8/dns-query"
	},
	{
	"tag": "local",
	"address": "local",
	"detour": "direct"
	},
	{
		"tag": "block",
	"address": "rcode://success"
	}
	],
	"strategy": "ipv4_only"
	},
    "inbounds": [
      {
        "type": "tun",
        "interface_name": "tun0",
        "domain_strategy": "ipv4_only",
        "inet4_address": "172.16.250.1/30",
        "auto_route": true,
        "strict_route": false,
        "sniff": true,
	"sniff_override_destination": true,
	"gso": true
     }
    ],
  "outbounds": [
    {
      "domain_strategy": "",
      "flow": "xtls-rprx-vision",
      "packet_encoding": "",
      "server": "XXXXXXXX",
      "server_port": 443,
      "tag": "vless-out",
      "tls": {
        "enabled": true,
        "reality": {
          "enabled": true,
          "public_key": "XXXXXXXX",
          "short_id": "XXXXXXXX"
        },
        "server_name": "google.com",
        "utls": {
          "enabled": true,
          "fingerprint": "random"
        }
      },
      "type": "vless",
      "uuid": "XXXXXXXX"
    },
	{
	"type": "dns",
	"tag": "dns-out"
	  },
	{
	"type": "direct",
	"tag": "direct"
	},
	{
	"type": "block",
	"tag": "block"
	}

  ],
   "route": {
    "rules": [
	{
	"protocol": "dns",
	"outbound": "dns-out"
	},
        {
         "ip_is_private": true,
        "outbound": "direct"
        },
		// Изгои не достойные смотреть синий трактор на ютюбе
        {
        "source_ip_cidr": [
		"10.10.0.13",
		"10.10.0.29",
		"10.10.0.220",
		"10.10.0.251"
			],
        "outbound": "direct"
        },
		// Исключаем Российские домены. Можно еще добавить GOV
        {
        "domain_suffix": [
		".ru",
		".su"
			],
        "outbound": "direct"
        },
	{
	"domain_suffix": "ntc.party",
	"outbound": "vless-out"
	},
        {
        "domain_suffix": "weather.com",
        "outbound": "vless-out"
        },
       {
         "rule_set": "antizapret",
         "outbound": "vless-out"
       },
       {
          "rule_set": "youtube",
          "outbound": "vless-out"
        },
       {
          "rule_set": "discord",
          "outbound": "vless-out"
        },
       {
          "rule_set": "discordipset",
          "outbound": "vless-out"
        },
       {
          "rule_set": "insta",
          "outbound": "vless-out"
        },
       {
          "rule_set": "meta",
          "outbound": "vless-out"
        }
	],
	// Этот список работы еле как. Но чет местами открывает ну и пускай будет.
"rule_set": [
      { // Remote rule-set will be cached if experimental.cache_file.enabled.
        "tag": "antizapret",
        "type": "remote",
        "format": "binary",
        "url": "https://github.com/savely-krasovsky/antizapret-sing-box/releases/latest/download/antizapret.srs",
        "download_detour": "vless-out",
        "update_interval": "1d"
      },
      {
         "tag": "youtube",
         "type": "local",
         "format": "source",
         "path": "/etc/sing-box/rule-set/youtube.json"
      },
      {
         "tag": "discord",
         "type": "local",
         "format": "source",
         "path": "/etc/sing-box/rule-set/discord.json"
      },
      {
         "tag": "discordipset",
         "type": "local",
         "format": "source",
         "path": "/etc/sing-box/rule-set/discordipset.json"
      },
      {
         "tag": "insta",
         "type": "local",
         "format": "source",
         "path": "/etc/sing-box/rule-set/instafasebook.json"
      },
      {
         "tag": "meta",
         "type": "local",
         "format": "source",
         "path": "/etc/sing-box/rule-set/meta.json"
      }
    ],

	"final": "direct",
	"auto_detect_interface": true
    }
  }

Пример правил /etc/sing-box/rule-set/youtube.json

{
    "version": 1,
    "rules": [
        {
            "domain_suffix":[
"1e100.net",
"ggpht.com",
"googleusercontent.com",
"googlevideo.com",
"gstatic.com",
"gvt1.com",
"l.google.com",
"m.youtube.com",
"nhacmp3youtube.com",
"play.google.com",
"wide-youtube.l.google.com",
"www.youtube.com",
"youtu.be",
"youtube-nocookie.com",
"youtube-studio.com",
"youtube-ui.l.google.com",
"youtube.be",
"youtube.ca",
"youtube.co",
"youtube.co.in",
"youtube.co.uk",
"youtube.com",
"youtube.com.au",
"youtube.com.br",
"youtube.com.mx",
"youtube.com.tr",
"youtube.com.ua",
"youtube.de",
"youtube.es",
"youtube.fr",
"youtube.googleapis.com",
"youtube.jp",
"youtube.nl",
"youtube.pl",
"youtube.pt",
"youtube.ru",
"youtubeapi.com",
"youtubechildren.com",
"youtubecommunity.com",
"youtubecreators.com",
"youtubeeducation.com",
"youtubeembeddedplayer.googleapis.com",
"youtubei.googleapis.com",
"youtubekids.com",
"yt-video-upload.l.google.com",
"yt.be",
"yt3.ggpht.com",
"ytimg.com",
"10tv.app",
"7tv.app",
"7tv.gg",
"7tv.io",
"api.7tv.app",
"cdn.7tv.app",
"cdn.7tv.gg",
"emotes.7tv.app",
"events.7tv.app",
"static.7tv.app"
]
        }
    ]
}


Добавляем автозагрузку sing-box

systemctl enable sing-box.service

Ну и делаем автоматическую перезагрузку сервиса.

  1. А ОТКУДА РОЖАТЬ ТАКИЕ RULES-SET??
    В тырнетах вы можете наткнуться на списки для zapret’a, но они лишь список.
instagramm.com
instagramn.com
instagrampartners.com
instagramphoto.com
instagramq.com
instagramsepeti.com
instagramtakipcisatinal.net

Есть два пути. 1. Прогнать этот список через онлайн редакторы/преобразователи и тому подобное и собрать конструкцию уже на месте.
2. Воспользоваться Python скриптом который изволил родить мне ChatGPT. Он ищет txt файлики в папке откуда его запустили и переделывает их в json c уже готовой конструкцией и даже определяет ip или домены вы добавляете.

import json
import ipaddress
import os

# Функция для чтения имен из текстового файла
def read_names_from_txt(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        names = [line.strip() for line in file if line.strip()]
    return names

# Функция для проверки, является ли строка IP-адресом или подсетью
def is_valid_ip_or_subnet(ip):
    try:
        ipaddress.ip_network(ip, strict=False)
        return True
    except ValueError:
        return False

# Функция для записи имен в JSON файл с нужной структурой
def write_names_to_json(names, json_file_path):
    data = {
        "version": 1,
        "rules": [
            {
                "ip_cidr": [],
                "domain_suffix": []
            }
        ]
    }

    for name in names:
        if is_valid_ip_or_subnet(name):
            data["rules"][0]["ip_cidr"].append(name)
        else:
            data["rules"][0]["domain_suffix"].append(name)

    with open(json_file_path, 'w', encoding='utf-8') as json_file:
        json.dump(data, json_file, ensure_ascii=False, indent=4)

# Основная программа
def main():
    current_directory = os.getcwd()  # Получаем текущую директорию

    # Ищем все текстовые файлы в текущей директории
    for filename in os.listdir(current_directory):
        if filename.endswith('.txt'):
            txt_file_path = os.path.join(current_directory, filename)
            json_file_path = os.path.splitext(txt_file_path)[0] + '.json'  # Сохраняем с тем же именем, но в формате JSON

            names = read_names_from_txt(txt_file_path)
            write_names_to_json(names, json_file_path)

            # Удаляем исходный текстовый файл
            os.remove(txt_file_path)
            print(f"Файл {txt_file_path} успешно преобразован в {json_file_path} и удалён.")

if __name__ == "__main__":
    main()

Где-то видел инфу, что сам sing-box что-то умеет, но я не разбирался. Мне лень :slight_smile:

На этом моя настройка малинки закончена. Всем кто участвовал в обсуждении СПАСИБО, вы оказали огромную помощь в настройке и теперь я могу дальше деградировать. :grinning:

Настройка Mikrotik
Есть 3 пути.

  1. Mangle.
    Изначально я выбрал именно этот путь. Но как показала практика это режет скорость тырнетов и если на скачивание это ±20%, то на выгрузку это 50%. Поэтому этот вариант отпадает. (Возможно, у вас стоит дикая тварь, которая может пережевать весь трафик без потерь. Но у меня точно нет)
  2. В DHCP сервере указать основным шлюзом малинку.
    Это самый надежный способ в плане потери пропускной способности. Но если что-то произойдет с малиной то тырнетов вам дома не видать, пока вы не поменяете шлюз на DHCP сервере и аренда не обновится. Главное не забудьте задать статический ip на малине.
  3. В DHCP сервере указать основным шлюзом малинку. НО делаем автоматическую проверку жизни малины и скрипт для смены основного шлюза
    1.Делаем привило Mangle на Output, метим все исходящие маршруты до адреса 4.2.2.2
    2.Добавляем маршрут в ip-routes, что все маршруты с этой меткой идут через малину.
    3. system-scripts добавляем скрипт, который при падении малины сам поменяет шлюз и перезагрузит все порты, а при восстановлении вернет все в зад.
:local GATEWAY "4.2.2.2"
:local COUNT "2"
:local REPLY "1"
:local STATUS  ([/ping $GATEWAY count=$COUNT]-$REPLY);
:if ($STATUS<0) do={
/log error message="vless not check"
/ip dhcp-server network set [find comment=main_server]  gateway=10.10.0.1
/interface ethernet set ether2 disabled=yes
/interface ethernet set ether3 disabled=yes
/interface ethernet set ether5 disabled=yes
/interface ethernet set ether6 disabled=yes
/interface ethernet set ether7 disabled=yes
/interface ethernet set ether8 disabled=yes
/interface ethernet set ether9 disabled=yes
:delay 3
/interface ethernet set ether2 disabled=no
/interface ethernet set ether3 disabled=no
/interface ethernet set ether5 disabled=no
/interface ethernet set ether6 disabled=no
/interface ethernet set ether7 disabled=no
/interface ethernet set ether8 disabled=no
/interface ethernet set ether9 disabled=no
} else {
/ip dhcp-server network set [find comment=main_server]  gateway=10.10.0.10
}

Малина имеет адрес 10.10.0.10, а сам микрот 10.10.0.1
Далее делаем задачу на запуск скрипта по времени ip-scheduler. Я запускаю скрипт каждые 30 сек.
При потери связи с малиной все пойдет через провайдера в течении 1 минуты максимум. А вот при восстановлении фильтрация появится в зависимости от срока аренды вашего DHCP сервера. Я поставил 5 мин.
Есть один минус - каждый раз будет в логи срать сообщениями о смене шлюза, но это мы переживем.
Ну и естественно не забываем про статику на малине и в лизесах залочить ip адрес малины

Итог:
В итоге мы имеем довольно надежную схему, которая не режет трафик от провайдера и не ест трафик на VPS т.к. него летят лишь отфильтрованные домены. Схема без особого труда масштабируется и поднимается за час времени с поднятием vps и обновлением малины.
Жду ваши комментарии и предложения. А также рассказы где я накосячил :slight_smile:
З.Ы. Потратил на написание этого текста больше времени, чем на поднятие схемы