Socks5Client
Socks5Client یک tunnel میانی stream است که سمت client پروتکل SOCKS5 را پیاده
میکند. این node از transport بعدی برای اتصال به SOCKS proxy استفاده میکند،
method negotiation را انجام میدهد، در صورت نیاز با username/password احراز هویت
میشود و سپس برای مقصد نهایی تنظیمشده درخواست CONNECT یا UDP ASSOCIATE
میفرستد.
مقصد نوشتهشده در تنظیمات Socks5Client همان مقصد نهایی داخل درخواست SOCKS
است. آدرس خود SOCKS proxy باید در connector بعدی تنظیم شود؛ معمولاً
TcpConnector برای TCP یا TcpUdpConnector در حالتی که relay مربوط به UDP لازم
است.
جایگاه رایج
برای TCP:
TcpListener -> Socks5Client -> TcpConnector
برای TCP و UDP از یک SOCKS server:
TcpUdpListener -> Socks5Client -> TcpUdpConnector
در چیدمان دوم، connector باید برای control connection از TCP استفاده کند و
relay address برگشتی از UDP ASSOCIATE را از طریق UDP در دسترس قرار دهد.
نمونه پایه
{
"name": "socks-client",
"type": "Socks5Client",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "tcp",
"user": "alice",
"pass": "secret",
"domain-strategy": "do-not-resolve-domains",
"verbose": false
},
"next": "proxy-transport"
}
proxy-transport باید nodeای باشد که به SOCKS5 server متصل میشود؛ برای مثال یک
TcpConnector که آدرس و port مربوط به proxy در آن تنظیم شده است.
پروتکل پویا
اگر line ورودی از قبل protocol مقصد را مشخص کرده است، میتوانید از
dest_context->protocol استفاده کنید تا client همان protocol را دنبال کند.
{
"name": "socks-client",
"type": "Socks5Client",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "dest_context->protocol"
},
"next": "proxy-transport"
}
اگر destination context دقیقاً TCP باشد، CONNECT فرستاده میشود و اگر دقیقاً
UDP باشد، UDP ASSOCIATE انجام خواهد شد. در نبود flag مناسب، در حالت مبهم یا
برای protocolی غیر از TCP/UDP، یک warning ثبت میشود و node به TCP برمیگردد.
مقصد ثابت
اگر میخواهید مقصد SOCKS را بهصورت ثابت در JSON بنویسید:
{
"name": "socks-client",
"type": "Socks5Client",
"settings": {
"address": "example.com",
"port": 443,
"protocol": "tcp",
"domain-strategy": "resolve-domains-and-prefer-ipv4"
},
"next": "proxy-transport"
}
با فعال بودن resolve محلی domain، DomainResolver داخلی پیش از فرستادن درخواست
SOCKS، example.com را resolve میکند. اگر پاسخ IPv4 موجود باشد، همان آدرس در
درخواست SOCKS قرار میگیرد؛ در غیر این صورت fallback مطابق strategy انتخابشده
انجام میشود.
فیلدهای اجباری
| فیلد | نوع | توضیح |
|---|---|---|
settings | object | اجباری است و نباید خالی باشد. |
settings.address / settings.target-address / settings.target | string | آدرس مقصد نهایی SOCKS. میتواند IP ثابت، domain با طول ۱ تا ۲۵۵ بایت، dest_context->address یا line->dest_ctx->address باشد. domain طولانیتر هنگام ساخت node رد میشود. |
settings.port | number یا string | پورت مقصد نهایی. عدد 1 تا 65535، یا dest_context->port / line->dest_ctx->port. |
next | string | اجباری است و به transport سمت SOCKS proxy اشاره میکند. |
فیلدهای اختیاری
| فیلد | پیشفرض | توضیح |
|---|---|---|
settings.protocol / settings.proto | tcp | مقدارهای مجاز: tcp, connect, udp, udp-associate, dest_context->protocol, line->dest_ctx->protocol. |
settings.user / settings.username | تنظیم نشده | نام کاربری SOCKS5. باید همراه pass / password بیاید. |
settings.pass / settings.password | تنظیم نشده | رمز SOCKS5. باید همراه user / username بیاید. |
settings.domain-strategy | do-not-resolve-domains | تعیین میکند domain پیش از درخواست SOCKS بهصورت local resolve شود یا همان domain ارسال گردد. |
settings.verbose | false | logهای debug بیشتری فعال میکند. |
username و password باید غیرخالی باشند و هر کدام حداکثر ۲۵۵ بایت طول داشته باشند. اگر فقط یکی از آنها تنظیم شود، ساخت node شکست میخورد.
Domain strategy
Socks5Client پیش از اجرای منطق SOCKS، target تنظیمشده را آماده میکند و
protocol انتخابشده را روی line اصلی client و line->dest_ctx نگه میدارد.
اگر domain-strategy resolve محلی را فعال کند، client یک DomainResolver داخلی
میسازد که prepare hook آن پیش از DNS resolution، target تنظیمشده را اعمال
میکند. این کار هم برای domain ثابت و هم برای مقداری که از
dest_context->address کپی شده انجام میشود؛ IPهای ثابت دستنخورده میمانند.
| مقدار | رفتار |
|---|---|
do-not-resolve-domains | domain بدون تغییر در درخواست SOCKS5 نوشته میشود تا SOCKS server آن را resolve کند. |
resolve-domains-with-core-settings | از تنظیم DNS داخل core.json استفاده میکند. |
resolve-domains-and-accept-dns-returned-order | اولین نتیجه قابل استفاده DNS را میگیرد. |
resolve-domains-and-prefer-ipv4 | IPv4 را ترجیح میدهد و در صورت نبود، IPv6 را استفاده میکند. |
resolve-domains-and-prefer-ipv6 | IPv6 را ترجیح میدهد و در صورت نبود، IPv4 را استفاده میکند. |
resolve-domains-and-use-only-ipv4 | فقط IPv4؛ اگر نتیجه IPv4 نباشد line بسته میشود. |
resolve-domains-and-use-only-ipv6 | فقط IPv6؛ اگر نتیجه IPv6 نباشد line بسته میشود. |
جهت مقداردهی اولیه
lineهای برنامه از طریق Init در جهت upstream وارد Socks5Client میشوند.
Init در جهت downstream برای همه نقشهای line، شامل application، control و relay
در حالت UDP، غیرفعال است؛ دریافت آن یک پیام fatal ثبت میکند و با
abortProgramNow(1) فرایند را خاتمه میدهد. callbackهای downstream برای Est
و payload همچنان برای پاسخهای proxy و دادههای relay پشتیبانی میشوند.
رفتار TCP
در حالت TCP، lifecycle به این ترتیب است:
- upstream
Init، line state مربوط بهSocks5Clientرا مقداردهی اولیه میکند وInitرا به transport بعدی میفرستد. - transport بعدی اتصال proxy را برقرار میکند و
Estبلافاصله، پیش از مذاکرهٔ SOCKS، به node قبلی میرسد. Socks5Clientgreeting مربوط به methodهای SOCKS5 را یک بار ارسال میکند.Estهای تکراری transport به برنامه منتقل میشوند، بدون ارسال دوبارهٔ greeting.- اگر credential تنظیم شده باشد و server روش username/password را انتخاب کند، درخواست احراز هویت فرستاده میشود.
- node برای مقصد تنظیمشده دستور
CONNECTمیفرستد. - پاسخ موفق، ارسال دادهٔ برنامه را آزاد میکند و
Estدوباره فرستاده نمیشود. - payloadهای upstream که در انتظار بودند، بدون انتظار برای Resume و با حفظ ترتیب FIFO روی اتصال proxy فرستاده میشوند.
اعلانهای Pause/Resume در TCP بلافاصله عبور میکنند، حتی اگر تکراری باشند یا هنگام مذاکره برسند. greeting و خروجی آمادهٔ برنامه یا پاسخ منتظر Resume نمیمانند. Pause دریافت، حتی هنگام مذاکره به adapter بعدی میرسد؛ فرستندهٔ آن باید خودش Resume یا بستن اتصال را ترتیب دهد.
پس از موفقیت مذاکرهٔ TCP، تخلیهٔ تمام ورودی و خروجی نگهداریشده و آزاد شدن توقف محلی منبع، client وضعیت line خود را نابود میکند و برای آن line بهطور دائمی bypass میشود. callbackهای بعدی مستقیماً بین همسایهها عبور میکنند. callback در حال اجرا پیش از استفاده از state که ممکن است در تبادل بازگشتی نابود شده باشد، فعال بودن node را بررسی میکند.
اگر پیش از موفق شدن درخواست SOCKS، دادهٔ upstream برسد، در صفی با محدودیت ۲ MiB و ۱۰۲۴ buffer نگه داشته میشود. حافظهٔ صف فقط هنگام اولین استفاده تخصیص مییابد. محدودیت ۴٬۰۹۶ بایت فقط برای metadata ناقص پاسخ است؛ یک پاسخ کامل میتواند همراه با بدنهٔ بزرگتر برنامه دریافت شود. پردازش بازگشتی پاسخها ترتیبی است و دادهٔ پاسخِ نگهداریشده بودجهٔ مشترک ۲ MiB و ۱۰۲۴ buffer در جهت پاییندست دارد. این صف پس از ورودی قدیمیتر پاسخ تخلیه میشود، حتی در حضور Pause؛ ارسال مستقیم TCP همچنان همگام است. ورودی خالی TCP از پراکسی نادیده گرفته میشود و باقیماندهٔ TCP روی control line مربوط به UDP پس از برقراری ارتباط دور ریخته میشود. lineهای UDP application و relay برای stream مربوط به handshake حافظه تخصیص نمیدهند.
اگر proxy همه methodها را رد کند، authentication شکست بخورد، پاسخ command نامعتبر باشد یا command با خطا تمام شود، tunnel آن line را میبندد.
رفتار UDP
در حالت UDP، line اصلی همان مسیر application است و Socks5Client دو line داخلی میسازد:
| line داخلی | کاربرد |
|---|---|
| UDP control line | یک line TCP که به SOCKS proxy وصل میشود و UDP ASSOCIATE را با مقصد 0.0.0.0:0 میفرستد. |
| UDP relay line | یک line UDP که به relay address برگشتی از proxy وصل میشود. |
relay line از BND.ADDR و BND.PORT پاسخ موفق UDP ASSOCIATE استفاده میکند.
این line، port کنترل TCP را دوباره بهکار نمیبرد و endpoint مذاکرهشده را با
address و port ثابت SOCKS proxy در connector بعدی جایگزین نمیکند. بنابراین server
میتواند یک relay port موقت و اختصاصی برگرداند؛ policy مربوط به firewall و NAT
باید endpoint برگشتی را اجازه دهد، نه فقط port کنترل TCP را.
این authority مذاکرهشده از پاکسازی metadata مربوط به protocolهای مشاهدهشده
توسط Router یا sniffer عبور میکند. adapter خروجی UDP آن را دقیقاً یکبار مصرف
میکند و سپس marker داخلی را پاک میکند تا روی routing نامرتبط بعدی اثر نگذارد.
Est مربوط به transport کنترل، یک بار به line برنامه منتقل میشود. ارسال datagram همچنان
تا آماده شدن control association و relay line منتظر میماند؛ صف آن به ۲ MiB و ۱۰۲۴ buffer محدود است. سپس Socks5Client یک header مربوط به SOCKS5 UDP و شامل مقصد نهایی
به ابتدای هر datagram اضافه میکند و آن را از relay line میفرستد. پاسخهای
relay اعتبارسنجی میشوند، header از آنها برداشته میشود و فقط payload به node
قبلی برمیگردد.
fragmentهای SOCKS5 UDP دوباره سرهم نمیشوند؛ datagramهایی که FRAG != 0
دارند برای احتیاط نادیده گرفته میشوند.
این مقدار padding اجازه میدهد header مربوط به SOCKS5 UDP بدون نقض قرارداد buffer به ابتدای datagram افزوده شود. اگر buffer ورودی باز هم فضای کافی در سمت چپ نداشته باشد، tunnel یک buffer جایگزین میگیرد و پیش از افزودن header، payload را در آن کپی میکند.
اشتباههای رایج
- آدرس proxy را داخل
settings.addressننویسید؛ این فیلد مقصد نهایی داخل request SOCKS است. - حالت UDP را با connector فقط-TCP به کار نبرید؛ این حالت هم control line از نوع TCP و هم relay line از نوع UDP میخواهد.
domain-strategyروی IP literal اثری ندارد.- وقتی از
dest_context->addressاستفاده میکنید،portهمچنان جداگانه لازم است.
پشتیبانی از splice
Socks5Client از payloadهای TCP مبتنی بر splice پشتیبانی میکند. هنگام مذاکرهٔ
روش احراز هویت، اعتبارسنجی و پاسخ command، کل هر ورودی با sbufEnsureOrdinary()
پیش از ورود به parser یا صف ورودی بازگشتی به حافظهٔ عادی تبدیل میشود. پس از
مذاکره، payloadهای TCP و ورودیهای لازم در صف FIFO میتوانند buffer اصلی را حفظ
کنند. محدودیت handshake ناقص از محدودیت backlog برنامه جدا میماند.
lineهای UDP application، TCP control و UDP relay در هر دو جهت خواندن عادی را برای تمام عمر خود درخواست میکنند. آمادهسازی مقصد UDP این ترجیح را پیش از نگهداری ورودی توسط DNS محلی تنظیم میکند. این ترجیح تضمین نوع buffer نیست و به lineهای مرتبط منتقل نمیشود؛ هر datagram همچنان پیش از نگهداری، خواندن header یا قالببندی کاملاً عادی میشود. ورودی عادی بدون تغییر عبور میکند. lineهای TCP CONNECT ترجیح خواندن عادی را درخواست نمیکنند.
DomainResolver داخلی قابلیت splice را حفظ میکند. فعالشدن واقعی به chain
نهاییِ واجد شرایط و misc.splice وابسته است؛ وجود TLS یا node مسدودکنندهٔ دیگر
splice را برای کل chain payload غیرفعال میکند.
متادیتای 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 | 262 بایت |
Est اتصال transport را اعلام میکند، نه موفقیت مذاکرهٔ SOCKS. برای UDP نیز Est کنترل TCP یک بار به line برنامه نگاشت میشود.
داده پس از Init پذیرفته میشود، اما تا آماده شدن پروتکل در صف محدود به ۲ MiB و ۱۰۲۴ buffer میماند.
در TCP، greeting و خروجی آمادهٔ برنامه یا پاسخ بدون انتظار برای Resume و با حفظ ترتیب FIFO فرستاده میشوند.
اولین دادهٔ نگهداریشده، منبع خود را Pause میکند و پس از تخلیهٔ صف قدیمیتر، Resume فرستاده میشود.
این توقف محلی پروتکل مستقل از فشار transport است که شفاف عبور داده میشود.
Pause دریافت در TCP حتی هنگام مذاکره به adapter بعدی میرسد؛ فرستندهٔ آن باید Resume یا بستن اتصال را ترتیب دهد.
در UDP، فشار مسیرهای کنترل و relay تجمیع میشود و Resume یکی، Pause دیگری را پاک نمیکند.
تخلیهٔ backlog برنامه و greeting خط کنترلِ تحت مالکیت node همچنان این فشار را رعایت میکنند؛
فشار دریافت برنامه پس از Init خط relay به همان خط منتقل میشود.
lineهای UDP application، control و relay برای قالببندی، مالکیت association و نگاشت فشار فعال میمانند.