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:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام یکتای node داخل config. |
type | string | باید دقیقاً "ReverseClient" باشد. |
next | string | اجباری در configهای عملی. مسیر outbound peer به سمت ReverseServer. |
settings میتواند حذف شود یا خالی باشد. اگر موجود باشد، باید object باشد.
تنظیمات اختیاری
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
minimum-unused | integer | workers * 4 | حداقل تعداد reverse link آماده برای هر tunnel. باید بزرگتر از 0 باشد. |
reverse-secret-length | integer | 640 | طول handshake داخلی. باید در بازه 1..1024 باشد. |
reverse-secret | ASCII string | unset | بایتهای 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 به remote | previous node -> ReverseClient -> next node |
| remote به local | next 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 از سمت previous | payload سمت 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 flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 0 بایت |
پشتیبانی 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 این قابلیت جای این بررسیها را نمیگیرد. بهبود کارایی باید با اندازهگیری تأیید شود.