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

Bridge

Bridge دو نقطه جدا در یک config WaterWall را به هم وصل می‌کند. این node payload را تغییر نمی‌دهد، socket باز نمی‌کند و پروتکل جدیدی اضافه نمی‌کند؛ فقط callback‌های line مثل Init، Payload، Finish، Pause و Resume را بین دو Bridge جفت‌شده، اما در جهت مخالف، عبور می‌دهد. Est نیز مانند دیگر callback‌های lifecycle با همین الگو عبور داده می‌شود.

چرا Bridge ساخته شد​

در زمان نگارش مستند اولیه، این node عملاً برای ساخت تونل معکوس (ReverseClient / ReverseServer) طراحی شد و هنوز هم رایج‌ترین کاربرد آن همین است.

در بیشتر node‌های لایهٔ transport در WaterWall، جهت حرکت «چپ به راست» است؛ یعنی اتصال از ابتدای chain شروع می‌شود و با next به سمت جلو می‌رود.

ساده‌ترین نمونه، port forwarding است:

برای ساخت reverse tunnel دو مشکل اصلی وجود داشت:

  • در سمت ReverseClient معمولاً listener نداریم؛ این node باید خودش اتصال‌های آماده به سمت ReverseServer ایجاد کند.
  • یک ReverseClient عملاً به دو مسیر نیاز دارد: مسیر peer و مسیر مقصد محلی مثل xray-core. اما مدل عادی WaterWall برای هر node فقط یک next دارد.

ایده اول: دو next برای ReverseClient​

یک ایده این بود که ReverseClient دو مسیر داخلی داشته باشد:

{
"name": "reverse-client",
"type": "ReverseClient",
"settings": {
"next-core": "local-core-path",
"next-peer": "remote-peer-path"
}
}

این طرح کنار گذاشته شد، چون ReverseClient را به یک node خاص با مدل chain اختصاصی تبدیل می‌کرد و از مدل استاندارد next دور می‌شد.

ایده پذیرفته‌شده: Bridge​

در طراحی نهایی، next خود ReverseClient برای مسیر peer استفاده می‌شود؛ یعنی مسیری که در نهایت به ReverseServer می‌رسد:

اما مسیر مقصد محلی، مثلاً xray-core، باید از «پشت» ReverseClient وصل شود. این کار با یک جفت Bridge انجام می‌شود:

به این ترتیب، هر داده‌ای که وارد یک Bridge شود انگار وارد Bridge جفتش شده است، اما از جهت مخالف خارج می‌شود. این همان چیزی است که برای reverse tunnel نیاز داشتیم، بدون اینکه مدل تک-next در WaterWall شکسته شود.

در سمت ReverseServer هم همین ایده استفاده می‌شود:

راهنمای پیکربندی​

دو Bridge باید به شکل جفت تنظیم شوند:

{
"name": "bridge-a",
"type": "Bridge",
"settings": {
"pair": "bridge-b"
},
"next": "path-a-next-node"
}
{
"name": "bridge-b",
"type": "Bridge",
"settings": {
"pair": "bridge-a"
},
"next": "path-b-next-node"
}

فیلد next از نظر JSON همیشه اجباری نیست، چون یک Bridge ممکن است در ابتدا یا انتهای یک شاخه قرار بگیرد. اما چیدمان باید طوری باشد که سمتی که Bridge می‌خواهد از آن خارج شود واقعاً همسایه داشته باشد.

فیلدهای اجباری​

فیلدهای سطح اصلی:

فیلدنوعتوضیح
namestringنام انتخابی node که باید در فایل config یکتا باشد.
typestringباید دقیقاً "Bridge" باشد.
settingsobjectباید فیلد pair را داشته باشد.

فیلد اجباری در settings:

فیلدنوعتوضیح
pairstringنام Bridge جفت در همان config.

فیلد اختیاری دیگری در settings وجود ندارد.

اگر pair وجود نداشته باشد یا node‌ای با آن نام پیدا نشود، راه‌اندازی شکست می‌خورد. برای رفتار قابل پیش‌بینی، دو Bridge را به‌صورت متقابل pair کنید:

bridge-a.settings.pair = bridge-b
bridge-b.settings.pair = bridge-a

رفتار جهت‌ها​

Bridge داده را تغییر نمی‌دهد؛ فقط جهت callback را برعکس می‌کند:

رویداد ورودی به Bridge فعلیفراخوانی روی Bridge جفتخروج از Bridge جفت
upstream Init / Est / Payload / Finish / Pause / ResumetunnelPrevDownStream*سمت previous جفت
downstream Init / Est / Payload / Finish / Pause / ResumetunnelNextUpStream*سمت next جفت

به همین دلیل Bridge شبیه آینه عمل می‌کند: مسیر را به Bridge جفت می‌فرستد و جهت WaterWall را برعکس می‌کند.

قواعد مجاورت نودهای Reverse​

هنگام استفاده از Bridge همراه node‌های reverse، اتصال‌های زیر باید مستقیم باشند:

  • Bridge -> ReverseClient
  • ReverseServer -> Bridge

هیچ node‌ای نباید در هیچ‌کدام از این دو فاصله قرار بگیرد. برای مثال، Bridge -> SpeedLimit -> ReverseClient و ReverseServer -> SpeedLimit -> Bridge ممنوع هستند. هرگونه پردازش اضافی را بیرون از این دو فاصله قرار دهید.

این قاعده فرض مالکیت node‌های عادی stream را حفظ می‌کند. downstream Init از ReverseClient مستقیماً به Bridge می‌رسد و در شاخهٔ جفت‌شده به upstream Init تبدیل می‌شود. Bridge همان line را عبور می‌دهد و مالک آن نمی‌شود. تغییر جهت باید در همین مرز بماند؛ node میانی‌ای که مالک line را در سمت prev فرض می‌کند نباید در این فاصله قرار بگیرد.

این‌ها قواعد طراحی chain هستند که کاربر باید رعایت کند؛ کد این الزامات مجاورت را به‌صورت خودکار بررسی نمی‌کند. این قواعد مربوط به همین اتصال‌ها با node‌های reverse هستند و به این معنا نیستند که هر Bridge باید همسایه‌ای از نوع reverse داشته باشد.

مثال کامل برای ReverseClient​

{
"name": "outbound_to_core",
"type": "TcpConnector",
"settings": {
"nodelay": true,
"address": "127.0.0.1",
"port": 443
}
}
{
"name": "bridge_core",
"type": "Bridge",
"settings": {
"pair": "bridge_reverse_client"
},
"next": "outbound_to_core"
}
{
"name": "bridge_reverse_client",
"type": "Bridge",
"settings": {
"pair": "bridge_core"
},
"next": "reverse_client"
}
{
"name": "reverse_client",
"type": "ReverseClient",
"settings": {
"minimum-unused": 4
},
"next": "outbound_to_peer"
}
{
"name": "outbound_to_peer",
"type": "TcpConnector",
"settings": {
"nodelay": true,
"address": "1.1.1.1",
"port": 443
}
}

در این ساختار، next خود ReverseClient مسیر peer است و مسیر core/local از طریق Bridge جفت‌شده وصل می‌شود.

مثال کامل برای ReverseServer​

{
"name": "users_inbound",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 443,
"nodelay": true
},
"next": "bridge_users"
}
{
"name": "bridge_users",
"type": "Bridge",
"settings": {
"pair": "bridge_reverse"
}
}
{
"name": "bridge_reverse",
"type": "Bridge",
"settings": {
"pair": "bridge_users"
}
}
{
"name": "reverse_server",
"type": "ReverseServer",
"settings": {},
"next": "bridge_reverse"
}
{
"name": "peers_inbound",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 8443,
"nodelay": true,
"whitelist": ["1.1.1.1/32"]
},
"next": "reverse_server"
}

در این مثال بهتر است listener مربوط به peer با whitelist یا لایه امنیتی معادل محدود شود. در سناریوی واقعی معمولاً TLS، mux، router یا لایه‌های دیگر هم به این مسیر اضافه می‌شوند.

Lifecycle و رفتار Line​

Bridge هیچ object از نوع line_t نمی‌سازد و آزاد نمی‌کند. همان pointer مربوط به line را از طریق Bridge جفت عبور می‌دهد و فضایی برای state مخصوص هر line اختصاص نمی‌دهد.

payload نیز بدون تغییر عبور داده می‌شود:

جهت ورود به Bridge فعلینوع ارسال از Bridge جفت
upstream payloaddownstream payload از Bridge جفت به سمت node previous آن
downstream payloadupstream payload از Bridge جفت به سمت node next آن

چون Bridge هیچ بایتی prepend نمی‌کند و فریم‌بندی ندارد، required_padding_left = 0 است.

نکته‌ها​

  • Bridge node adapter نیست و به address یا port وصل نمی‌شود.
  • payload را فریم‌بندی، prepend، رمزنگاری، decode یا بازنویسی نمی‌کند.
  • required_padding_left آن 0 است.
  • packet و stream را به یکدیگر تبدیل نمی‌کند؛ فقط callback‌های WaterWall را بین دو نقطه عبور می‌دهد.
  • Bridge جفت باید در همان فایل config و زیر همان تنظیمات node manager وجود داشته باشد.
  • بدون pair و چیدمان درست شاخه‌ها، Bridge رفتار مفیدی ندارد.

متادیتای نود​

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

ویژگیمقدار
node flagskNodeFlagChainHead | kNodeFlagChainEnd | kNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayerAnything
layer_group_prev_nodekNodeLayerAnything
layer_group_next_nodekNodeLayerAnything
required_padding_left0 bytes

پشتیبانی splice​

در هر دو جهت معتبر، همان buffer و همان line با نگاشت فعلی جهت callback از Bridge جفت عبور می‌کنند. کل توپولوژی ادغام‌شده باید واجد شرایط باشد. Bridge مالک line عبوری نمی‌شود.

buffer‌های عادیِ fallback همچنان پذیرفته می‌شوند. splice به build پشتیبانی‌شدهٔ Linux، فعال‌بودن misc.splice، نبود node packet و پشتیبانی همهٔ node‌های chainِ نهایی پس از گسترش و ادغام نیاز دارد. flag قابلیت این بررسی‌ها را دور نمی‌زند. بدون اندازه‌گیری، بهبود کارایی تضمین نمی‌شود.

config packet این node دولایه همچنان splice را فعال نمی‌کند.