پرش به مطلب اصلی

SniffRouter

SniffRouter برای سناریوهای رایج domain sniffing در لایه ۴ طراحی شده است. این node نخستین upstream payload هر connection را بررسی می‌کند، بر اساس بایت‌های قابل مشاهده تصمیم می‌گیرد و تمام connection را از نخستین route مناسب عبور می‌دهد.

بیشتر تنظیمات HTTP/1 Host و TLS ClientHello SNI را می‌توان با node انعطاف‌پذیرتر Router نیز پیاده کرد. وقتی یک نگاشت کوتاه از domain به مقصد می‌خواهید، SniffRouter مناسب است. برای HTTP/2 یا HTTP/3 sniffing، تطبیق source/destination IP یا port، ‏GeoIP/GeoSite، هویت احراز‌شده، شرط‌های ترکیبی و سناریوهای پیشرفته‌تر routing از Router استفاده کنید.

مدل ذهنی​

  • upstream Init بلافاصله به شاخه‌ها فرستاده نمی‌شود.
  • node منتظر اولین upstream payload می‌ماند.
  • payload نخست به‌طور موقت در buffer نگه داشته می‌شود.
  • route‌ها به ترتیب JSON بررسی می‌شوند.
  • اولین route‌ای که match شود انتخاب می‌شود.
  • اگر هیچ route‌ای match نشود، مسیر top-level next استفاده می‌شود.
  • شاخه انتخاب‌شده مقداردهی اولیه می‌شود و بایت‌های bufferشده روی همان شاخه دوباره پخش می‌شوند.
  • payload‌های بعدی دیگر دوباره classify نمی‌شوند.
هشدار

SniffRouter برای انتخاب مسیر باید بایت‌هایی از سمت client ببیند. در protocol‌های server-first تا رسیدن upstream payload هیچ شاخه‌ای انتخاب نمی‌شود.

جایگاه رایج​

بعد از TLS termination برای HTTP Host:

TcpListener -> TlsServer -> SniffRouter
|-- host a.example.com -> backend_a
|-- host b.example.com -> backend_b
`-- default next -> camouflage_site

قبل از TLS termination برای SNI:

TcpListener -> SniffRouter
|-- SNI a.example.com -> tls_site_a
|-- SNI b.example.com -> tls_site_b
`-- default next -> default_tls_site

نمونه تنظیم​

HTTP Host routing:

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{
"domains": [
"a.example.com",
"b.example.com"
],
"next": "example_backend"
},
{
"domains": [
"*.static.example.com"
],
"next": "static_backend"
}
]
},
"next": "default_backend"
}

TLS SNI routing:

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{
"domains": [
"site-a.example.com"
],
"next": "tls_site_a"
},
{
"domains": [
"site-b.example.com"
],
"next": "tls_site_b"
}
]
},
"next": "default_tls_site"
}

با حذف detection، هر دو روش HTTP/1 Host و TLS ClientHello SNI برای آن route فعال می‌شوند. فقط وقتی route باید به یکی از این دو روش محدود شود، مقدار "http1" یا "tls" را صریح بنویسید.

فیلدهای اصلی​

فیلدنوعتوضیح
namestringنام node.
typestringباید دقیقاً "SniffRouter" باشد.
nextstringاجباری؛ مسیر fallback وقتی هیچ route‌ای match نشود.

settings می‌تواند حذف شود یا خالی باشد. در نبود route، همه connection‌ها به next می‌روند.

تنظیمات​

گزینهنوعپیش‌فرضتوضیح
routesarrayunsetفهرست route‌ها به ترتیب اولویت. اگر نوشته شود باید آرایه‌ای غیرخالی باشد.

Route object​

فیلدنوعضروریتوضیح
domainsstring یا arrayبلهالگوهای domain برای تشخیص HTTP/1 Host و TLS SNI.
domainstringنهalias برای یک domain. با domains هم‌زمان استفاده نشود.
detectionstring یا arrayنهحذف آن یعنی هر دو مقدار ["http1", "tls"].
nextstringبلهtarget node برای route.
targetstringنهalias برای route next.

مقدارهای detection:

مقدارمعنی
http1خواندن HTTP/1 Host؛ به‌صورت پیش‌فرض فعال است.
tlsخواندن TLS ClientHello SNI؛ به‌صورت پیش‌فرض فعال است.

مقدارهای حذف‌شده:

مقدار قدیمیمقدار جدید
httphttp1
client-hellotls
tls-client-hellotls

ترتیب Route‌ها​

route‌ها به ترتیب نوشته‌شده در JSON بررسی می‌شوند و نخستین match برنده است. route‌های خاص را قبل از wildcard‌ها قرار دهید.

{
"routes": [
{
"domain": "api.example.com",
"next": "api_backend"
},
{
"domain": "*.example.com",
"next": "wildcard_backend"
}
]
}

در این مثال، api.example.com به api_backend می‌رود، چون route دقیق پیش از wildcard آمده است. اگر ترتیب برعکس بود، wildcard در صورت تطبیق الگو آن را می‌گرفت. توجه کنید که *.example.com با subdomain‌ها مطابقت دارد، اما با خود example.com نه.

Domain Matching​

matching مربوط به domain به حروف کوچک و بزرگ حساس نیست. الگوهای config به lowercase تبدیل می‌شوند و نقطه انتهایی حذف می‌شود.

patternمعنی
example.commatch دقیق.
*.example.comبا subdomain‌هایی مانند www.example.com مطابقت دارد، اما با خود example.com نه.
*هر Host/SNI غیرخالی.

الگوهای نامعتبر:

  • رشته خالی
  • *. بدون suffix
  • wildcard‌هایی غیر از فرم *.example.com
  • URL مثل https://example.com
  • host:port مثل example.com:443
  • مقدارهای شامل / یا \

اگر HTTP Host روی wire شامل port باشد، sniffer آن را پیش از matching حذف می‌کند؛ برای نمونه Host: example.com:443 با الگوی example.com مطابقت دارد.

HTTP Host Detection​

اگر فیلد detection حذف شود، تشخیص HTTP/1 Host برای route فعال است:

{
"domains": [
"app.example.com"
],
"next": "app_backend"
}

در حالت http1، SniffRouter انتظار دارد نخستین payload قابل مشاهده یک request از نوع HTTP/1 باشد. سپس header مربوط به Host را می‌خواند و آن را با الگوهای domain در route مقایسه می‌کند.

برای HTTPS معمولاً SniffRouter را بعد از TlsServer می‌گذاریم:

TcpListener -> TlsServer -> SniffRouter

preface مربوط به HTTP/2 cleartext، HTTP/1 Host ندارد؛ بنابراین در نبود match از روش دیگری، ترافیک به next می‌رود.

TLS SNI Detection​

اگر فیلد detection حذف شود، تشخیص TLS ClientHello SNI نیز فعال است:

{
"domains": [
"*.example.com"
],
"next": "tls_branch"
}

وقتی این route باید فقط TLS را بررسی کند و HTTP/1 Host را نپذیرد، مقدار "detection": "tls" را صریح بنویسید.

این حالت معمولاً قبل از TLS termination استفاده می‌شود:

TcpListener -> SniffRouter -> TlsServer

اگر SNI پیدا نشود یا با route‌ها match نشود، مسیر fallback یعنی top-level next استفاده می‌شود.

sniffer از ClientHello تقسیم‌شده میان چند TLS record پشتیبانی می‌کند، حتی اگر مرز record داخل handshake header یا SNI باشد. بازسازی handshake تقسیم‌شده حداکثر 8192 بایت حافظه دارد و ورودی ناقص همچنان تابع محدودیت sniff window است. ClientHello کامل در یک record از مسیر مستقیم parse می‌شود.

buffer payload اول​

SniffRouter بایت‌های ابتدایی upstream را تا زمان تعیین route در buffer نگه می‌دارد.

برای HTTP Host و TLS SNI، SniffRouter می‌تواند تا کامل شدن request یا ClientHello مقدار بیشتری byte بخواهد. پنجره sniffing مشترک محدود است:

8192 bytes

قوانین دقیق دسته‌بندی و buffering:

  • پیام کامل ClientHello در یک record ابتدا ارزیابی می‌شود، حتی اگر اندازه کل پیام بزرگتر از ۸۱۹۲ بایت باشد. بازسازی handshake تقسیم‌شده میان چند record سقف ۸۱۹۲ بایت دارد.
  • اگر ورودی هنوز ناقص باشد و مجموع بایت‌های دریافت شده به سقف ۸۱۹۲ بایت برسد، SniffRouter انتظار را متوقف کرده و شاخه پیش‌فرض next را انتخاب می‌کند.
  • پیام‌های ناقصی که کمتر از ۸۱۹۲ بایت دارند در حالت تصمیم‌گیری‌نشده باقی می‌مانند؛ SniffRouter تایمر اختصاصی ندارد و timeout‌های listener اولیه (مانند TcpListener) مهلت اتصال را تعیین می‌کنند.
  • بایت‌های اولیهٔ stream بدون تغییر و به‌ترتیب FIFO به شاخهٔ انتخاب‌شده فرستاده می‌شوند؛ مرز packetهای TCP یا callbackها لزوماً حفظ نمی‌شود. در بازپخش UDP مرز هر datagram، حتی datagram خالی، حفظ می‌شود.

پس از انتخاب route، payload‌های بعدی مستقیماً به target انتخاب‌شده یا next پیش‌فرض می‌روند و connection دوباره بررسی نمی‌شود.

در زمان مقداردهی اولیهٔ شاخه و بازپخش داده‌های اولیه، ترتیب FIFO حفظ می‌شود: ورودی‌ای که از callbackهای تودرتو می‌رسد پشت بایت‌های قبلاً پذیرفته‌شده منتظر می‌ماند. برای stream، این ورودی اضافی در یک buffer عادی با سقف ۲ MiB بایت منطقی جمع می‌شود؛ عبور از این سقف line را می‌بندد. این محدودیت، نخستین ورودی کامل و قبلاً پذیرفته‌شده را صرفاً به‌دلیل بزرگ‌تر بودن از پنجرهٔ sniff رد نمی‌کند. SniffRouter نوع UDP ورودی را هنگام Init و پیش از تغییر context مقصد در شاخهٔ انتخابی ثبت می‌کند. هر datagram جداگانه نگه داشته می‌شود و یک نمای بایتی مستقل برای sniffing استفاده می‌شود. سقف FIFO برابر ۲ MiB payload، مجموع ۴ MiB ظرفیت bufferها و ۱۰۲۴ datagram است؛ datagram خالی نیز از سهم تعداد و ظرفیت استفاده می‌کند. عبور از هر سقف فقط سمت‌های مقداردهی‌اولیه‌شده را می‌بندد. بازپخش، محتوا، مرز و ترتیب datagramها را حتی برای ورودی تودرتوی زمان راه‌اندازی حفظ می‌کند. پس از پایان بازپخش اولیه، payloadهای بعدی بدون صف خروجی در SniffRouter عبور می‌کنند.

مسیریابی TLS SNI و استتار Nginx​

node SniffRouter می‌تواند پیش از TlsServer قرار گیرد تا مقدار SNI در TLS ClientHello را بررسی کند و دامنه‌های منطبق را به مسیر خاتمه TLS محافظت‌شده بفرستد، در حالی که سایر اتصالات را به یک سرور پوششی واقعی (مانند Nginx HTTPS) هدایت کند.

TcpListener :443 -> SniffRouter
|-- route (expected SNI) -> TlsServer -> VlessServer -> ...
`-- default (unmatched) -> TcpConnector -> real nginx :443

نمونه config:

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{
"domain": "vpn.example.com",
"detection": "tls",
"next": "protected-tls-server"
}
]
},
"next": "nginx-fallback-connector"
}

نکات کلیدی معماری​

  • تطابق SNI: اتصالاتی که TLS ClientHello معتبر با SNI منطبق ارائه دهند به protected-tls-server هدایت می‌شوند.
  • مسیر fallback پیش‌فرض: اتصالاتی با SNI نامنطبق، فاقد SNI، ورودی نامعتبر یا ترافیک Plaintext HTTP همگی به next در سطح اصلی (nginx-fallback-connector) فرستاده می‌شوند.
  • چرا fallback واقعی TLS در Nginx: هدایت ترافیک پیش‌فرض به سرور واقعی Nginx تضمین می‌کند که handshake‌های با SNI ناشناخته، بدون SNI و درخواست‌های plaintext پاسخ‌های معتبر و طبیعی Nginx (مانند خطای 400 "The plain HTTP request was sent to HTTPS port") دریافت کنند و از ایجاد اثر انگشت مصنوعی در رد handshake جلوگیری شود.
  • برای مشاهده config کامل، فایل tests/examples/vless_tls_sni_camouflage_server.json را بررسی کنید.

Target Branch‌ها​

next یا target در هر route نام یک node واقعی در همان config است. SniffRouter هنگام ساخت chain، target‌ها را وارد chain خودش می‌کند تا:

  • target‌ها line state داشته باشند.
  • downstream traffic از branch‌ها دوباره از SniffRouter برگردد.
  • lifecycle chain حفظ شود.

قوانین target:

  • target node باید وجود داشته باشد
  • target نمی‌تواند خود SniffRouter باشد
  • شاخه target نباید از قبل به previous ناسازگاری متصل شده باشد
  • قرار گرفتن در chain مشترک، node داخل شاخهٔ تنظیم‌شدهٔ دیگر را به target معتبر تبدیل نمی‌کند؛ شاخهٔ پیش‌فرض و targetهای صریح تکرارشده، همراه helperهای داخلی آن‌ها، معتبر می‌مانند
  • target می‌تواند همان node مربوط به top-level next باشد

اگر شاخه در بخش دیگری از config قرار دارد، یک Bridge را به‌عنوان target قرار دهید.

lifecycle​

callbackرفتار
upstream Initفقط state داخلی SniffRouter را مقداردهی اولیه می‌کند.
اولین upstream Payloadداده را در buffer نگه می‌دارد، route را انتخاب و target/default را مقداردهی اولیه می‌کند و سپس داده را بازپخش می‌کند.
payload‌های بعدیمستقیم به target یا default می‌روند.
upstream Pause / Resumeپس از انتخاب route، حتی در صورت تکرار، به شاخهٔ انتخابی می‌روند؛ پیش از انتخاب نادیده گرفته می‌شوند و مانع بازپخش payload نیستند.
upstream Estفقط به شاخه پیش‌فرض فرستاده می‌شود. target‌های route اغلب node‌های adapter/server هستند که upstream Est برای آن‌ها معتبر نیست.
upstream Finishstate داخلی را از بین می‌برد و شاخه انتخاب‌شده را با Finish می‌بندد.
downstream callbacksبه previous node برمی‌گردند.
downstream Finishپیش از فرستادن Finish به previous node، state داخلی را از بین می‌برد.

Pause/Resume در هر دو جهت advisory هستند. SniffRouter وضعیت Pause مصرف‌کننده را نگه نمی‌دارد و برای تکمیل راه‌اندازی شاخه یا تحویل ورودی آماده منتظر Resume نمی‌ماند.

پس از بازگشت Init شاخهٔ پیش‌فرض و پایان بازپخش اولیه، SniffRouter state مربوط به line را از بین می‌برد، کاملاً صفر می‌کند و خودش را برای آن line به‌طور دائمی bypass می‌کند. routeهای target همچنان فعال می‌مانند تا انتخاب شاخه و جلوگیری از ارسال upstream Est حفظ شود؛ حتی اگر target پیکربندی‌شده همان top-level next باشد. bypass مقصد انتخابی را تغییر نمی‌دهد و nodeهای بعدی را از chain حذف نمی‌کند.

SniffRouter یا Router؟​

برای domain sniffing ساده HTTP/1، در حالتی که node قبلی هنوز destination domain را تعیین نکرده است، دو تنظیم از نظر مفهومی بسیار شبیه‌اند. این SniffRouter سه domain را به سه مقصد جدا می‌فرستد:

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{ "domain": "one.example.com", "next": "destination_one" },
{ "domain": "two.example.com", "next": "destination_two" },
{ "domain": "three.example.com", "next": "destination_three" }
]
},
"next": "default_destination"
}

تنظیم معادل با Router چنین است:

{
"name": "router",
"type": "Router",
"settings": {
"sniffing": ["http1"],
"rules": [
{ "destination-domain": "one.example.com", "target": "destination_one" },
{ "destination-domain": "two.example.com", "target": "destination_two" },
{ "destination-domain": "three.example.com", "target": "destination_three" }
]
},
"next": "default_destination"
}

در هر دو حالت، ورودی‌ها به‌ترتیب بررسی می‌شوند و top-level next مسیر fallback است. SniffRouter از domain/domains و next در route استفاده می‌کند؛ Router از destination-domain و target در rule استفاده می‌کند و می‌تواند شرط‌های دیگری را نیز به همان rule اضافه کند.

از SniffRouter استفاده کنید اگر فقط این‌ها را لازم دارید:

  • HTTP Host routing
  • TLS SNI routing
  • تنظیم کوتاه‌تر برای یک domain split ساده

اگر این قابلیت‌ها را می‌خواهید، مستندات Router را ببینید:

  • domain sniffing برای HTTP/2 یا QUIC/HTTP/3
  • source/destination IP یا port
  • GeoIP یا GeoSite
  • username/password
  • ترکیب protocol یا attributes با شرط‌های دیگر
  • چند شرط ترکیبی با AND یا rule‌های انعطاف‌پذیرتر

SniffRouter header تازه‌ای به ابتدای payload اضافه نمی‌کند و به left padding نیاز ندارد.

متادیتای node​

متادیتای برگرفته از کد منبع:

ویژگیمقدار
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left0 بایت

پیشرفته: مسیریابی handshake تونل Reverse​

این قابلیت فقط برای topology یک reverse tunnel واقعی است که node‌های ReverseClient و ReverseServer را دارد. این گزینه یک روش عمومی برای برعکس کردن جهت traffic یا callback‌های WaterWall نیست. در این سناریو، ReverseClient لینک‌های outbound آماده می‌سازد و handshake خصوصی را در ابتدای هر لینک قرار می‌دهد؛ chain‌های معمولی که این node‌ها را ندارند چنین handshake‌ای تولید نمی‌کنند و نباید این detector را تنظیم کنند.

این روش زمانی کاربرد دارد که یک TLS entrypoint رمزگشایی‌شده هم لینک‌های آماده تونل و هم سایت camouflage با SNI یکسان را حمل می‌کند:

TcpListener -> TlsServer -> SniffRouter
|-- private link handshake -> ReverseServer
`-- default next -> TcpConnector nginx

تنظیمات پیشرفته مربوط به این قابلیت:

گزینهنوعپیش‌فرضتوضیح
reverse-secret-lengthinteger640طول handshake خصوصی؛ بازه معتبر 1..1024.
reverse-secretASCII stringunsetsecret مربوط به XOR برای ساخت handshake خصوصی؛ باید در هر دو node peer یکسان باشد.

در route از "detection": "reverse" استفاده کنید. مقدارهای "reverse-tls" و "reverse-handshake" alias هستند. route‌ای که فقط از این detector استفاده می‌کند به domain یا domains نیاز ندارد و الگوهای domain موجود را نادیده می‌گیرد. اگر همین route با http1 یا tls ترکیب شود، domain‌ها برای آن روش‌های مبتنی بر domain همچنان ضروری‌اند.

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"reverse-secret-length": 640,
"reverse-secret": "shared-secret",
"routes": [
{
"detection": "reverse",
"next": "reverse_server"
}
]
},
"next": "camouflage_site"
}

signature پیش‌فرض ۶۴۰ بایت 0xFF است. SniffRouter مقدار سفارشی را دقیقاً با همان الگوریتم ReverseClient و ReverseServer می‌سازد، بنابراین تنظیمات هر سه node باید یکسان باشند. SniffRouter را بعد از TLS termination قرار دهید تا handshake از بایت صفر stream رمزگشایی‌شده شروع شود. SniffRouter فقط بایت‌ها را می‌بیند و دست‌نخورده بازپخش می‌کند؛ ReverseServer دوباره آن‌ها را اعتبارسنجی و حذف می‌کند. prefix ناقص در payload اول یک warning ثبت می‌کند و فوراً به top-level next می‌رود. همچنین header مربوط به PROXY protocol باید پیش از SniffRouter حذف شود. Router در حال حاضر این detector را ندارد.