You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Пытаюсь поднять SNI Router без Docker. Всё на одном VPS: mtg, Haproxy и Caddy с маскировочным сайтом, больше ни для чего он (VPS) не используется. Ориентировался на статьи SNI Router Setup и Surviving Active Probing на вики, конфиги из contrib/sni-router и issue #490. Ну и немного DeepSeek.
Зарегистрировал домен, прописал ему A и AAAA записи с IPv4 и IPv6 адресами сервера соответственно. Везде в конфигах и при генерации секретного ключа в mtg использовал только его.
Однако при попытке зайти на маскировочный сайт из браузера (по домену) получаю ошибку 403 Forbidden. Насколько я понимаю, так быть не должно. Может кто подсказать, что я не так настроил? Я, к сожалению, что-то среднее между эникеем и мамкиным сисадмином, и как бы не старался - найти причину не смог.
P.S. Попутно хочу уточнить, правильно ли я понимаю принцип работы SNI Router? Сейчас я понимаю его так:
Первым соединение принимает Haproxy, он является первым эшелоном защиты прокси от сканирования (Active Probing). Он смотрит на SNI, и если он отличается от искомого домена, то сразу направляет его (соединение) на маскировочный сайт. Если же тут порядок, то направляет на mtg.
mtg принимает соединение, и если он понимает, что оно не от Telegram, то направляет его на маскировочный сайт. В противном случае он работает как MTProxy.
Заранее благодарен.
Информация о системе и ПО
ОС Ubuntu 24.04
mtg 2.2.8
go1.22.2
mise 2026.5.6
Caddy v2.11.3
Haproxy 2.8.16-0ubuntu0.24.04.2
Включенный UFW
Всё ПО обновлено до последних версий (насколько они есть в репозиториях, я всё кроме mtg ставил через apt).
Содержание конфигов
mtg:
# Debug starts application in debug mode. It starts to be quite verbose
# in output. Actually, the idea is that you run it in debug mode only if
# you have any issue.
debug = false
secret = "my-hex-secret"
bind-to = "127.0.0.1:3128"
# Required because HAProxy prepends a PROXY protocol v2 header.
# Drop this line AND the `send-proxy-v2` from haproxy.cfg if you do
# not need real client IPs at the mtg layer.
proxy-protocol-listener = false
# Defines how many concurrent connections are allowed to this proxy.
# All other incoming connections are going to be dropped.
concurrency = 8192
# Sometimes you want to enforce mtg to use some types of
# IP connectivity to Telegram. We have 4 modes:
# - prefer-ipv6:
# We can use both ipv4 and ipv6 but ipv6 has a preference
# - prefer-ipv4:
# We can use both ipv4 and ipv6 but ipv4 has a preference
# - only-ipv6:
# Only ipv6 connectivity is used
# - only-ipv4:
# Only ipv4 connectivity is used
prefer-ip = "prefer-ipv4"
# Public IP addresses of this server. Used by 'mtg access' to generate
# proxy links and by 'mtg doctor' to validate SNI-DNS match.
# If not set, mtg tries to detect them automatically via ifconfig.co.
# Set these if ifconfig.co is unreachable from your server.
public-ipv4 = "my-IPv4"
public-ipv6 = "my-IPv6"
# If this setting is set, then mtg will try to get proxy updates from Telegram
# Usually this is completely fine to have it disabled, because mtg has a list
# of some core proxies hardcoded.
auto-update = false
# FakeTLS can compare timestamps to prevent probes. Each message has
# encrypted timestamp. So, mtg can compare this timestamp and decide if
# we need to proceed with connection or not.
#
# Sometimes time can be skewed so we accept all messages within a
# time range of this parameter.
tolerate-time-skewness = "5s"
# Telegram has a concept of DC. You can think about DC as a number of a cluster
# with a certain purpose. Some clusters serve media, some - messages, some rule
# channels and so on. But sometimes unknown DC number is requested by client.
# It could be a bug or some global reconfiguration of the Telegram.
#
# By default, proxy rejects such requests. But it is also possible to fallback
# this request to any DC. Telegram works in a way that any DC is able to serve
# any request but sacrificing a latency.
#
# If this setting is disabled (default), mtg will reject a connection.
# Otherwise, chose a new DC.
allow-fallback-on-unknown-dc = false
[domain-fronting]
ip = "127.0.0.1"
port = 8443
# proxy-protocol = true
[network]
# Only socks5 proxy is used. user/password is optional. As you can
# see, you can specify some parameters in GET query. These parameters
# configure circuit breaker.
proxies = [
# "socks5://user:password@host:port"
]
[network.timeout]
# network timeouts define different settings for timeouts. tcp timeout
# define a global timeout on establishing of network connections. idle
# means a timeout on pumping data between sockset when nothing is
# happening.
tcp = "5s"
http = "10s"
idle = "5m"
handshake = "10s"
[network.keep-alive]
# this defines a configuration for TCP keep alives. Default values are taken
# from Golang default behavior.
disabled = false
# idle means a time period after which we start sending TCP Keep Alive probes
idle = "15s"
# interval is a period between 2 consecutive probes
interval = "15s"
# if we miss that many probes, a connection will be considered as a dead one.
count = 9
[defense.anti-replay]
enabled = true
max-size = "1mib"
error-rate = 0.001
[defense.blocklist]
# You can enable/disable this feature.
enabled = false
# This is a limiter for concurrency. In order to protect website
# from overloading, we download files in this number of threads.
download-concurrency = 2
# A list of URLs in FireHOL format (https://iplists.firehol.org/)
# You can provider links here (starts with https:// or http://) or
# path to a local file, but in this case it should be absolute.
#
# NOTE: the default list below (firehol_level1.netset) includes bogon
# networks, and therefore RFC1918 ranges as well (10.0.0.0/8,
# 172.16.0.0/12, 192.168.0.0/16). If you run mtg on a home/LAN network
# and connect from a client on the same LAN, that client will be
# rejected with "ip was blacklisted" and the connection dropped (TCP
# close, no response). If you see this, you can either disable this section
# (enabled = false), replace firehol_level1 with a narrower list that
# does not include bogons (e.g. firehol_abusers_1d), or connect via
# a public IP/domain with hairpin NAT on your router. See README for
# details.
urls = [
"https://iplists.firehol.org/files/firehol_level1.netset",
# "/local.file"
]
# How often do we need to update a blocklist set.
update-each = "24h"
[stats.statsd]
# enabled/disabled
enabled = false
# host:port for UDP endpoint of statsd
address = "127.0.0.1:8888"
# prefix of metric for statsd
metric-prefix = "mtg"
# tag format to use
# supported values are 'datadog', 'influxdb' and 'graphite'
# default format is graphite.
tag-format = "datadog"
[stats.prometheus]
# enabled/disabled
enabled = false
# host:port where to start http server for endpoint
bind-to = "127.0.0.1:3129"
# prefix of http path
http-path = "/"
# prefix for metrics for prometheus
metric-prefix = "mtg"
Haproxy:
global
log /dev/log local0 info
log /dev/log local1 notice
maxconn 20000
nbthread 4
user haproxy
group haproxy
daemon
defaults
log global
mode tcp
option tcplog
option dontlognull
option tcpka # forward TCP keepalive to backend
timeout connect 5s
timeout client 1h
timeout server 1h
timeout client-fin 30s
timeout tunnel 1h # cap on a half-closed tunnel
retries 3
# --- HTTP :80 — ACME challenges + redirect ----------------------------------
frontend http
bind *:80
bind :::80
mode http
option httplog
# Pass /.well-known/acme-challenge/* straight to the web server so
# Let's Encrypt HTTP-01 validation works.
acl is_acme path_beg /.well-known/acme-challenge/
http-request redirect scheme https code 301 unless is_acme
use_backend web_acme if is_acme
# --- TLS :443 — SNI-based routing -------------------------------------------
frontend tls
bind *:443
bind :::443
tcp-request inspect-delay 5s
# Hard-reject anything that is not a TLS ClientHello.
tcp-request content reject if !{ req_ssl_hello_type 1 }
tcp-request content accept if { req_ssl_hello_type 1 }
# Custom log-format that records the SNI we routed on.
log-format "%ci:%cp [%t] %ft %b/%s %Tw/%Tc/%Tt %B %ts %ac/%fc/%bc/%sc/%rc sni=%[ssl_fc_sni]"
use_backend mtg if { req_ssl_sni -i my-domain.ru }
default_backend web
backend mtg
option tcp-check
server mtg 127.0.0.1:3128 check inter 30s
backend web
option tcp-check
server web 127.0.0.1:8443 check inter 30s
backend web_acme
mode http
server web 127.0.0.1:8080
bazis@spb-3-vm-----:~$ mtg doctor /etc/mtg.toml
Deprecated options
✅ All good
Time skewness
✅ Time drift is 7.506635ms, but tolerate-time-skewness is 5s
Validate native network connectivity
✅ DC 1
❌ DC 2: dial tcp [2001:67c:4e8:f002::a]:443: i/o timeout
✅ DC 3
✅ DC 4
✅ DC 5
✅ DC 203
Validate fronting domain connectivity
✅ 127.0.0.1:8443 is reachable
Validate SNI-DNS match
✅ IP address my-IPv6 matches secret hostname my-domain.ru
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Проблема
Пытаюсь поднять SNI Router без Docker. Всё на одном VPS: mtg, Haproxy и Caddy с маскировочным сайтом, больше ни для чего он (VPS) не используется. Ориентировался на статьи SNI Router Setup и Surviving Active Probing на вики, конфиги из contrib/sni-router и issue #490. Ну и немного DeepSeek.
Зарегистрировал домен, прописал ему A и AAAA записи с IPv4 и IPv6 адресами сервера соответственно. Везде в конфигах и при генерации секретного ключа в mtg использовал только его.
Однако при попытке зайти на маскировочный сайт из браузера (по домену) получаю ошибку 403 Forbidden. Насколько я понимаю, так быть не должно. Может кто подсказать, что я не так настроил? Я, к сожалению, что-то среднее между эникеем и мамкиным сисадмином, и как бы не старался - найти причину не смог.
P.S. Попутно хочу уточнить, правильно ли я понимаю принцип работы SNI Router? Сейчас я понимаю его так:
Заранее благодарен.
Информация о системе и ПО
Всё ПО обновлено до последних версий (насколько они есть в репозиториях, я всё кроме mtg ставил через apt).
Содержание конфигов
mtg:
Haproxy:
Caddy:
UFW:
Настройки Sysctl:
Логи
Результат mtg doctor:
Haproxy:
Caddy:
Beta Was this translation helpful? Give feedback.
All reactions