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

ReverseClient

ReverseClient سمت client در reverse tunnel است. این node تعدادی connection ازپیش‌بازشده و آماده به سمت ReverseServer نگه می‌دارد. هر زمان سمت server به یک مسیر واقعی نیاز داشته باشد، یکی از این connection‌ها از pool خارج و به یک line در سمت local متصل می‌شود.

این node معمولاً روی ماشینی قرار می‌گیرد که امکان برقراری اتصال outbound دارد، اما نمی‌تواند اتصال مستقیم inbound بپذیرد.

چرا در این ساختار از Bridge استفاده می‌شود​

در بیشتر chain‌های WaterWall مسیر ساده چپ به راست است:

اما ReverseClient شکل متفاوتی دارد. از یک طرف باید connection‌های آماده به سمت peer یعنی ReverseServer بسازد. از طرف دیگر، وقتی یکی از آن connection‌ها فعال شد، باید آن را به مقصد local مثل xray-core یا یک سرویس TCP محلی وصل کند.

چون در مدل WaterWall هر node فقط یک next دارد، به‌جای اضافه کردن فیلدهای خاصی مثل next-peer و next-core، از یک جفت Bridge استفاده می‌شود:

در این ساختار:

  • ReverseClient.next مسیر peer است که در نهایت به ReverseServer می‌رسد.
  • مسیر مقصد local از سمت قبلی ReverseClient و با جفت Bridge متصل می‌شود.
  • با فعال شدن reverse link، ReverseClient سمت local را برای node قبلی مقداردهی اولیه می‌کند.

برای طراحی reverse topology، صفحه Bridge را هم بخوانید.

این node چه می‌کند؟​

  • reverse link‌های outbound را از سمت next ایجاد می‌کند.
  • روی هر reverse link یک handshake داخلی می‌فرستد.
  • برای هر worker تعداد مشخصی connection آماده و استفاده‌نشده نگه می‌دارد.
  • connection‌های در حال connect و connection‌های آماده را جداگانه می‌شمارد.
  • وقتی سمت remote روی یکی از reverse link‌ها payload واقعی بفرستد، آن link را از pool خارج و جفت می‌کند.
  • با مصرف شدن یک link آماده، line جفت‌شده رو به local را برای node قبلی مقداردهی اولیه می‌کند.
  • payload، finish، pause و resume را بین دو سمت paired عبور می‌دهد.
  • بعد از مصرف یا بسته شدن connection، ظرفیت آماده را دوباره پر می‌کند.
  • connection آماده‌ای را که حدود ۳۰ ثانیه بدون استفاده بماند می‌بندد و جایگزین می‌کند.

ReverseClient نه listener معمولی است و نه connector معمولی. line‌های داخلی را خودش می‌سازد و با callback‌های عادی next/previous در WaterWall، مسیر peer را به مسیر local متصل می‌کند.

هم‌جواری الزامی در chain​

node Bridge باید دقیقاً قبل از ReverseClient قرار بگیرد: Bridge -> ReverseClient. هیچ node‌ای نباید بین این دو قرار بگیرد. در سمت server نیز Bridge باید دقیقاً بعد از ReverseServer باشد: ReverseServer -> Bridge، بدون هیچ node واسط.

این‌ها قواعد طراحی chain هستند و کاربر باید هنگام طراحی آن‌ها را رعایت کند. کد هیچ‌یک از این دو شرط هم‌جواری را به‌صورت خودکار بررسی و اعمال نمی‌کند. node‌های transport یا پردازش باید بیرون از این دو فاصله قرار بگیرند؛ مسیر outbound بعد از ReverseClient همچنان می‌تواند شامل node‌های transport توضیح‌داده‌شده در ادامه باشد.

جایگاه رایج​

Bridge(local target side) <-> Bridge(to ReverseClient) -> ReverseClient -> outbound peer path

نمونه ساده:

مسیر outbound peer می‌تواند TCP ساده باشد یا شامل TLS، HTTP، Reality، Mux یا سایر transport node‌ها باشد، تا زمانی که در نهایت به ReverseServer متناظر برسد.

نمونه تنظیم​

{
"name": "reverse-client",
"type": "ReverseClient",
"settings": {
"minimum-unused": 16,
"reverse-secret-length": 640,
"reverse-secret": "shared-secret"
},
"next": "outbound-to-reverse-server"
}

نمونه کامل با Bridge:

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

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

فیلدهای top-level:

فیلدنوعتوضیح
namestringنام یکتای node داخل config.
typestringباید دقیقاً "ReverseClient" باشد.
nextstringاجباری در config‌های عملی. مسیر outbound peer به سمت ReverseServer.

settings می‌تواند حذف شود یا خالی باشد. اگر موجود باشد، باید object باشد.

تنظیمات اختیاری​

گزینهنوعپیش‌فرضتوضیح
minimum-unusedintegerworkers * 4حداقل تعداد reverse link آماده برای هر tunnel. باید بزرگ‌تر از 0 باشد.
reverse-secret-lengthinteger640طول handshake داخلی. باید در بازه 1..1024 باشد.
reverse-secretASCII stringunsetبایت‌های handshake پیش‌فرض را با این secret به‌صورت تکرارشونده XOR می‌کند. وقتی تنظیم شود باید ASCII غیرخالی باشد.

ReverseClient، ReverseServer و هر SniffRouter که reverse link را تشخیص می‌دهد باید مقدارهای یکسانی برای reverse-secret-length و reverse-secret داشته باشند.

در مستندات قدیمی آمده بود که تغییر handshake به ویرایش source نیاز دارد. در نسخه فعلی این دو گزینه مستقیماً از config قابل تنظیم‌اند.

handshake داخلی​

به‌صورت پیش‌فرض هر reverse link با این payload شروع می‌شود:

640 bytes of 0xFF

اگر reverse-secret تنظیم شده باشد، هر byte پیش‌فرض با byte متناظر از secret به شکل تکرارشونده XOR می‌شود.

این handshake بخشی از داده کاربر نیست؛ تنها به ReverseServer کمک می‌کند reverse link‌های آماده را از connection‌های local/user تشخیص دهد.

رفتار startup و Pool​

هنگام startup، ReverseClient روی همه worker‌ها reverse link‌های outbound می‌سازد تا تعداد connection‌های آماده به مقدار هدف برسد.

برای هر worker:

  • reverse link‌های در حال connect
  • reverse link‌های establish‌شده ولی هنوز استفاده‌نشده

را جداگانه دنبال می‌کند.

اگر مجموع این دو از minimum-unused کمتر باشد، node ساخت reverse connection‌های بیشتری را روی آن worker زمان‌بندی می‌کند.

وقتی سمت next پیام Est را اعلام کند، link فقط اگر هنوز connecting باشد به pool آماده اضافه می‌شود. فعال‌سازی می‌تواند پس از Init مسیر transport و پیش از Est برسد؛ تکمیل آن، link را مستقیم از connecting به active می‌برد. Est دیرهنگام یا تکراری فقط آمادگی transport را ثبت می‌کند و شمارنده‌های pair فعال را تغییر نمی‌دهد یا spare جایگزین دیگری ایجاد نمی‌کند.

جریان فعال‌سازی​

reverse link آماده پس از دریافت کامل پیام فعال‌سازی یک‌باره از ReverseServer فعال می‌شود. header چهار بایتی big-endian طول کل پیام را مشخص می‌کند؛ طول شامل header است و باید در بازه 4..65536 باشد. padding به‌صورت تدریجی با cache چهار بایتی و scratch برابر 256 بایت کنار گذاشته می‌شود و تخصیص حافظه به اندازه طول اعلام‌شده لازم نیست. header یا padding تکه‌تکه و داده برنامه همراه پیام پشتیبانی می‌شوند. طول نامعتبر pair را می‌بندد؛ deadline موجود ۳۰ ثانیه‌ای پیام ناقص را هم پوشش می‌دهد.

بعد از مصرف پیام، link از شمارنده connecting یا unused و idle table خارج می‌شود، شمارنده active افزایش می‌یابد، spare جایگزین زمان‌بندی می‌شود و line مقصد به سمت previous node مقداردهی اولیه می‌شود. مقصد می‌تواند حتی بدون دریافت داده از user، greeting بفرستد. header و padding به مقصد نمی‌رسند. داده باقی‌مانده به ترتیب عبور می‌کند و همه readهای بعدی مستقیم‌اند. تنظیم بازه طول در ReverseClient لازم نیست. داده بازگشتی در حین Init مقصد پشت دادهٔ قدیمی در FIFO قرار می‌گیرد. بدنهٔ اولیه و ورودی‌های صف‌شده از یک بودجه با سقف 2 MiB بایت منطقی و 1024 buffer استفاده می‌کنند. ورودی خالی entry مصرف نمی‌کند؛ overflow، pair را می‌بندد. payload آماده مستقیم عبور می‌کند و از این backlog استفاده نمی‌کند.

بعد از pair شدن:

مسیرجریان
local به remoteprevious node -> ReverseClient -> next node
remote به localnext node -> ReverseClient -> previous node

callback‌های Pause و Resume، شامل پیام‌های تکراری، میان دو line جفت‌شده عبور داده می‌شوند. پیش از Init مقصد، آخرین Pause مسیر transport نگه داشته می‌شود و Resume آن را لغو می‌کند. Pause باقی‌مانده پس از بازگشت Init و پیش از بدنه اولیه به مقصد اعلام می‌شود. داده پذیرفته‌شده حتی در حالت Pause به ترتیب FIFO تخلیه می‌شود. node فعال می‌ماند، چون هر تحویل میان دو line متفاوت نگاشت می‌شود.

رفتار Finish و جایگزینی​

اگر یک paired link از هر سمت بسته شود:

  • هر دو line state داخلی از بین می‌روند
  • سمت peer با Finish بسته می‌شود
  • line‌های داخلی را ReverseClient از بین می‌برد، چون خودش آن‌ها را ساخته است
  • شمارنده active کاهش می‌یابد
  • ساخت یک reverse link جایگزین زمان‌بندی می‌شود

اگر یک reverse link آماده قبل از pair شدن بسته شود:

  • شمارنده‌های connection‌های در حال اتصال یا استفاده‌نشده به‌روز می‌شوند
  • idle-table entry آن حذف می‌شود
  • هر دو line داخلی از بین می‌روند
  • ساخت یک جایگزین زمان‌بندی می‌شود

اگر یک link آماده حدود ۳۰ ثانیه (30 seconds) استفاده نشده باشد، idle timeout آن را می‌بندد و جایگزین می‌کند.

جهت‌ها و lifecycle​

ReverseClient line‌های داخلی خودش را می‌سازد. upstream یا downstream init مستقیم از خارج بخشی از مسیر عادی این node نیست.

callbackرفتار
upstream Initغیرفعال؛ به‌عنوان misuse fatal در نظر گرفته می‌شود.
downstream Initغیرفعال؛ به‌عنوان misuse fatal در نظر گرفته می‌شود.
downstream Est از سمت nextآمادگی transport را ثبت می‌کند؛ link هنوز connecting را فقط یک بار به unused می‌برد.
downstream Payload از سمت nextیک reverse link آماده را فعال می‌کند یا payload را به line جفت‌شده سمت previous می‌فرستد.
upstream Payload از سمت previouspayload سمت local را به reverse link outbound جفت‌شده می‌فرستد.
Finishهر دو line داخلی را برای paired link‌ها می‌بندد؛ counters را پاک و ظرفیت را جایگزین می‌کند.

ReverseClient بعد از handshake، payload framing اضافه‌ای انجام نمی‌دهد، پس به left padding نیاز ندارد.

نکته‌های عملی​

  • minimum-unused میان latency و ظرفیت تعادل ایجاد می‌کند. مقدار بیشتر، connection‌های outbound آماده بیشتری نگه می‌دارد.
  • connection‌های از قبل باز شده عمداً idle هستند. آن‌ها را به‌عنوان connection leak در نظر نگیرید.
  • تنظیمات reverse secret را در هر دو peer یکسان نگه دارید.
  • برای متصل کردن مرتب سمت مقصد local از Bridge استفاده کنید.
  • مسیر outbound peer می‌تواند شامل transport/security node‌های دیگر باشد، اما باید byte-stream connectivity به ReverseServer متناظر را حفظ کند.

متادیتای node​

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

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

پشتیبانی splice​

handshake به شکل ordinary ساخته می‌شود. parser فعال‌سازی فقط بایت‌های prefix را با helperهای آگاه از representation مصرف می‌کند؛ بدنه برنامه همراه آن می‌تواند private pipe خود را حفظ کند. داده آماده در هر دو جهت بدون تغییر عبور می‌کند. ورودی‌های بازگشتی هنگام شروع، representation عادی یا private pipe خود را در FIFO حفظ می‌کنند. بسته شدن در Init مقصد داده نگه‌داشته‌شده را دقیقاً یک بار آزاد می‌کند. هر دو reverse node از chain واجد شرایط splice پشتیبانی می‌کنند؛ مجاورت مستقیم Bridge -> ReverseClient حفظ می‌شود.

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