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

Socks5Server

Socks5Server سمت server پروتکل SOCKS5 را در یک chain WaterWall پیاده می‌کند. این node ترافیک control را از node قبلی می‌گیرد، method را مذاکره می‌کند، از طریق AuthenticationClient یا حالت صریح no-auth احراز هویت را انجام می‌دهد و درخواست‌های SOCKS5 را به ترافیک WaterWall تبدیل می‌کند.

این node از این موارد پشتیبانی می‌کند:

  • method negotiation در SOCKS5
  • username/password authentication از طریق AuthenticationClient
  • دستور CONNECT
  • دستور UDP ASSOCIATE با تخصیص پورت پویا از طریق UdpListener یا TcpUdpListener
  • UDP relay association وابسته به یک TCP control connection پذیرفته‌شده

دستور BIND پشتیبانی نمی‌شود و رد می‌شود.

پذیرش از cache محلی​

پس از نخستین همگام‌سازی معتبر جدول و مبنای ساعت، authenticationclientCanAuthenticate() در قطع شبکه و احراز هویت دوباره از آخرین cache حافظه استفاده می‌کند؛ cache مهلت کهنگی ندارد. enabled، انقضا، سهمیه ترافیک و reservation محلی اتصال/IP همچنان اعمال می‌شوند. نبود cache، توقف، خطای نهایی continuity یا رد صریح اعتبارنامه خود کلاینت، پذیرش جدید را می‌بندد. فقط پاسخ موفق و معتبر Session رد صریح را پاک می‌کند؛ timeout، قطع اتصال، پاسخ خراب و authentication-required چنین اثری ندارند. اتصال‌های قبلاً مجاز به ثبت مصرف و اجرای محدودیت‌های محلی ادامه می‌دهند. هر درخواست HTTP روی اتصال reused پذیرش تازه است؛ exchange جاری و CONNECT ادامه می‌یابند. ویرایش مدیر پس از refresh معتبر می‌رسد. دو کلاینت قطع‌شده با مصرف شناخته‌شده ۸۰ از سهمیه ۱۰۰ گیگابایت می‌توانند هر کدام ۲۰ مصرف کنند و مجموع نهایی ۱۲۰ شود؛ سهمیه و reservation محلی، تضمین سراسری نیستند.

جایگاه رایج​

برای TCP:

TcpListener -> Socks5Server -> TcpConnector

برای حالت ترکیبی CONNECT و UDP:

TcpUdpListener -> [optional middle tunnels] -> Socks5Server -> TcpUdpConnector

فقط یک مسیر callback قبلی وجود دارد، نه دو chain مستقل TCP و UDP که به یک Socks5Server برسند. TcpUdpListener در همین یک مسیر هم control connection TCP و هم provider خصوصی UDP پویا را فراهم می‌کند. اگر udp: true باشد و مسیر قبلی نهایی‌شده provider از نوع UdpListener/TcpUdpListener نداشته باشد، راه‌اندازی شکست می‌خورد.

توپولوژی الزامی برای SOCKS5 UDP

برای استقرار عادی با node‌های داخلی WaterWall از TcpUdpListener استفاده کنید. TcpListener به‌تنهایی می‌تواند control connection مربوط به SOCKS5 TCP و دستور CONNECT را حمل کند، اما endpoint پویای UDP موردنیاز UDP ASSOCIATE را فراهم نمی‌کند. یک UdpListener مستقل قابلیت provider را ارائه می‌دهد، اما به‌تنهایی نمی‌تواند control connection الزامی TCP را بپذیرد؛ بنابراین فقط در یک توپولوژی سفارشی fan-in کاربرد دارد که TCP را روی همان مسیر callback فراهم کند.

Socks5Server این provider را به‌صورت خودکار از مسیر قبلی نهایی‌شدهٔ خود پیدا می‌کند. هیچ setting در JSON برای نام provider یا provider API وجود ندارد.

در حالت احرازشده، UserController را دستی پس از Socks5Server قرار ندهید؛ node خودش یک UserController داخلی می‌سازد و پیش از next وارد chain می‌کند.

نمونهٔ کامل توپولوژی TCP/UDP​

این config حداقلی هر سه نقش الزامی در مسیر داده را نشان می‌دهد: listener ترکیبی ورودی، node پروتکل SOCKS5 و connector‌ای که مقصد درخواستی هر اتصال TCP یا UDP را دنبال می‌کند.

{
"name": "socks5-service",
"nodes": [
{
"name": "socks-entry",
"type": "TcpUdpListener",
"settings": {
"address": "127.0.0.1",
"port": 1080,
"nodelay": true
},
"next": "socks-server"
},
{
"name": "socks-server",
"type": "Socks5Server",
"settings": {
"no-auth": true,
"connect": true,
"udp": true,
"ipv4": "127.0.0.1"
},
"next": "outbound"
},
{
"name": "outbound",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"nodelay": true,
"fastopen": false
}
}
]
}

این نمونه no-auth عمداً فقط روی loopback در دسترس است. برای client‌های راه دور، auth-client-node-name را همراه یک AuthenticationClient تنظیم کنید و سپس آدرس listener و آدرس اعلام‌شده را متناسب با deployment تغییر دهید.

آدرس‌ها و پورت‌های این توپولوژی نقش‌های متفاوتی دارند:

Setting یا مقدارمفهوم
TcpUdpListener.settings.addressآدرس محلی‌ای که listener نوع TCP و socket‌های پویای UDP روی آن bind می‌شوند.
TcpUdpListener.settings.portپورت عمومی control مربوط به SOCKS5 TCP. child استاتیک UDP داخلی نیز روی همین پورت گوش می‌دهد، اما Socks5Server این پورت ثابت را به‌عنوان association نوع SOCKS UDP نمی‌پذیرد.
Socks5Server.settings.ipv4آدرس IPv4‌ای که در BND.ADDR به کلاینت SOCKS5 اعلام می‌شود؛ کلاینت باید بتواند به آن دسترسی داشته باشد. این مقدار setting مربوط به bind socket نیست.
BND.PORT در پاسخپورت relay نوع UDP که سیستم‌عامل برای هر association به‌صورت پویا انتخاب می‌کند و در پاسخ UDP ASSOCIATE برمی‌گرداند.

نمونهٔ node authenticated​

{
"name": "socks-server",
"type": "Socks5Server",
"settings": {
"auth-client-node-name": "auth-client",
"connect": true,
"udp": true,
"ipv4": "203.0.113.10",
"sweep-interval-ms": 1000,
"verbose": false
},
"next": "outbound"
}

auth-client باید نام node‌ای موجود از نوع AuthenticationClient در همان config باشد. Socks5Server از آن برای lookup استفاده می‌کند و خودش authentication client تازه‌ای نمی‌سازد.

203.0.113.10 را با آدرس IPv4‌ای جایگزین کنید که به کلاینت‌های SOCKS5 اعلام می‌شود و آن کلاینت‌ها بتوانند به آن دسترسی داشته باشند.

نمونه no-auth​

{
"name": "socks-server",
"type": "Socks5Server",
"settings": {
"no-auth": true,
"connect": true,
"udp": false
},
"next": "outbound"
}

دقیقاً یکی از دو حالت احراز هویت باید انتخاب شود: auth-client-node-name یا no-auth: true.

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

فیلدنوعتوضیح
settingsobjectاجباری است و نباید خالی باشد.
settings.auth-client-node-namestringاجباری است مگر اینکه no-auth صریحاً true باشد. باید به یک node AuthenticationClient موجود اشاره کند و نمی‌تواند خود همین Socks5Server باشد.
settings.no-authbooleanفقط وقتی لازم است که auth client ندارید؛ برای فعال شدن حالت بدون احراز هویت باید true باشد.
settings.ipv4 / settings.udp-ipv4stringوقتی udp فعال است اجباری است. آدرس IPv4 قابل‌دسترسی که در BND.ADDR پاسخ UDP ASSOCIATE اعلام می‌شود؛ این مقدار آدرس bind مربوط به listener نیست.
nextstringاجباری است و به مسیر outbound واقعی اشاره می‌کند.

حداقل یکی از connect یا udp باید فعال باشد.

وقتی udp فعال است، مسیر قبلی نهایی‌شده باید یک provider پویای UDP ارائه دهد. در توپولوژی عادی با node‌های داخلی، یعنی سرویس را با TcpUdpListener به‌جای TcpListener آغاز کنید.

فیلدهای اختیاری​

فیلدپیش‌فرضتوضیح
settings.connecttrueفعال‌سازی CONNECT.
settings.udpfalseفعال‌سازی UDP ASSOCIATE و پردازش datagram‌های UDP.
settings.verbosefalselog بیشتر، از جمله دلیل رد شدن auth.
settings.sweep-interval-ms1000به UserController داخلی در حالت authenticated پاس داده می‌شود.

تنظیمات قدیمی username، password، users و accounts دیگر پذیرفته نمی‌شوند. اگر هرکدام از این کلیدها وجود داشته باشد، ساخت node شکست می‌خورد. برای احراز هویت از AuthenticationServer همراه AuthenticationClient استفاده کنید، یا صریحاً no-auth: true بگذارید.

مدل احراز هویت​

در حالت authenticated، client باید روش username/password را در SOCKS5 پیشنهاد کند. Socks5Server username و password را می‌گیرد و یک رشته lookup با این فرمت می‌سازد:

username:password

سپس همین رشته را از طریق AuthenticationClient به‌عنوان password کاربر lookup می‌کند.

برای user‌هایی که در AuthenticationServer برای SOCKS5 می‌سازید، جفت credential را دقیقاً با همین فرم username:password داخل فیلد password قرار دهید. فیلد name کاربر برای username روی پروتکل SOCKS5 استفاده نمی‌شود و می‌تواند صرفا metadata مدیریتی باشد.

بعد از auth موفق، هویت user و metadata سازگار مربوط به credential به Waterwall line اضافه می‌شود. UserController داخلی پیش از رسیدن line به next، policy تنظیم‌شده user را اعمال می‌کند. در حالت no-auth هویت authenticated برای policy مختص user وجود ندارد؛ بنابراین access control در سطح deployment همچنان مهم است.

username و password در حالت احرازشده باید غیرخالی باشند. NUL داخلی رد می‌شود، چون lookup نهایی به‌صورت C string انجام می‌شود.

رفتار CONNECT​

بعد از negotiation و authentication، درخواست CONNECT به یک مسیر stream عادی Waterwall تبدیل می‌شود:

  1. مقصد درخواستی SOCKS داخل line->dest_ctx کپی می‌شود.
  2. protocol مقصد TCP تنظیم می‌شود.
  3. Init به سمت node بعدی ارسال می‌شود.
  4. دادهٔ upstream پس از احراز هویت و Init سمت بعدی، حتی پیش از Est، قابل ارسال است.
  5. هر Est از سمت بعدی، حتی اعلان تکراری، بلافاصله به node قبلی فرستاده می‌شود؛ پاسخ موفق دوباره تولید نمی‌شود.
  6. پاسخ موفق SOCKS5 پیش از بدنهٔ پاسخ ارسال می‌شود؛ صف آمادهٔ پاسخ و بدنه با ترتیب FIFO و بدون انتظار برای Resume تخلیه می‌شود.

اگر connect غیرفعال باشد، درخواست CONNECT با command not supported رد می‌شود.

رفتار UDP ASSOCIATE​

UDP ASSOCIATE یک association می‌سازد که مالک آن TCP control line است. این command از همان control line یک stream upstream عادی باز نمی‌کند.

provider با پیمایش مسیر callback قبلی نهایی‌شده به‌صورت خودکار پیدا می‌شود. tunnel‌های میانی اختیاری می‌توانند بین TcpUdpListener و Socks5Server باقی بمانند، به شرطی که آن مسیر همچنان به provider برسد. هیچ setting مربوط به provider به JSON node Socks5Server اضافه نمی‌شود.

پاسخ UDP associate از این دو مقدار ساخته می‌شود:

  • آدرس settings.ipv4 یا settings.udp-ipv4 به‌عنوان آدرس پاسخ
  • پورت اختصاصی موقت (ephemeral port) که به‌صورت پویا توسط UdpListener (یا TcpUdpListener) قبلی برای این کلاینت bind و آماده شده است

هنگام دریافت UDP ASSOCIATE، node Socks5Server از لیسنر UDP قبلی یک endpoint پویای اختصاصی درخواست می‌کند و پس از باز شدن socket و فعال‌سازی آن روی worker فعلی، پورت اختصاص‌یافته را در پاسخ UDP ASSOCIATE برمی‌گرداند. اگر bind، فعال‌سازی یا تخصیص پاسخ شکست بخورد، فقط همان association با پاسخ general-failure رد می‌شود و association‌های قبلی سالم می‌مانند. endpoint برگشتی باید از مسیر firewall و NAT قابل‌دسترسی باشد. ترافیک UDP روی port ثابت/استاتیک به‌عنوان SOCKS association پذیرفته نمی‌شود.

در UDP Init و دوباره پیش از نخستین payload، metadata endpoint باید WID واقعی مالک line و handle فعال association را نشان دهد. mismatch مربوط به metadata/WID در حالی که worker فعلی درست است، فقط UDP را fail-closed رد می‌کند: datagram را drop می‌کند، پاسخی نمی‌فرستد و TCP control line را بدون تغییر باقی می‌گذارد. اجرای callback روی worker فعلیِ نادرست یک invariant هسته‌ای و fatal است و پیش از خواندن line state یا resource محلی worker رد می‌شود.

مدل امنیتی UDP​

listener مربوط به UDP یک open proxy نیست. datagram تنها در این شرایط پذیرفته می‌شود:

  • روی socket اختصاصی و داینامیک تخصیص‌یافته به همان association کلاینت رسیده باشد
  • IP/port مبدأ datagram با آدرس peer مورد انتظار یا pin‌شده مطابقت داشته باشد
  • association توسط یک TCP control line معتبر ایجاد شده و روی worker جاری فعال باشد

اگر IP مشخص‌شده در درخواست UDP-associate با IP مربوط به TCP control peer متفاوت باشد، درخواست رد می‌شود. IP wildcard از همان control peer استفاده می‌کند و اگر source port صفر باشد، نخستین datagram مطابق، port را pin می‌کند. SOCKS5 استاندارد برای هر datagram احراز هویت رمزنگاری‌شده ندارد؛ در نتیجه peer دیگری با همان IP عمومی مورد انتظار که port اختصاصی را بداند، می‌تواند هنگام port صفر برای نخستین packet مسابقه دهد. مقاومت قوی‌تر به یک encapsulation یا token احراز هویت‌شدهٔ UDP با طراحی جداگانه نیاز دارد.

وقتی TCP control line بسته شود، endpoint داینامیک UDP بسته شده و association از جدول worker-local حذف می‌شود و هرگونه packet بعدی رد خواهد شد.

state مربوط به association محلی worker است و از یک هویت opaque برای dynamic endpoint استفاده می‌کند. این state به‌جای pointer قابل استفاده به TCP control line، metadata کپی‌شده نگه می‌دارد و هنگام بسته شدن control line فقط association فعلی و مطابق همان line حذف می‌شود.

رفتار UDP payload​

وقتی یک datagram SOCKS UDP برسد:

  1. association فعال با استفاده از هویت opaque مربوط به dynamic endpoint پیدا می‌شود.
  2. header مربوط به SOCKS5 UDP اعتبارسنجی می‌شود.
  3. اگر FRAG != 0 باشد، datagram محافظه‌کارانه نادیده گرفته می‌شود.
  4. مقصد remote از هدر خوانده می‌شود.
  5. هدر SOCKS5 UDP حذف و payload با destination context درخواستی به سمت upstream ارسال می‌شود.

وقتی پاسخ از node بعدی برگردد:

  1. payload در یک header پاسخ SOCKS5 UDP قرار می‌گیرد.
  2. source address داخل header، endpoint راه دور مرتبط با همان پاسخ را مشخص می‌کند.
  3. datagram حاصل در جهت downstream به سمت socket پویای UDP فرستاده می‌شود.

association سمت client از پردازش outbound جدا می‌ماند و از قرارداد callback عادی لایه ۴ استفاده می‌کند. state مربوط به outbound همان association هنگام بسته شدن control line آزاد می‌شود.

رفتار Finish​

پیاده‌سازی ترتیب close در Waterwall را حفظ می‌کند:

  • line state محلی پیش از فرستادن Finish واقعی از بین می‌رود
  • control line قبل از مرگ، endpoint پویای UDP خود را می‌بندد و association خود را unregister می‌کند
  • state مربوط به outbound همان association پیش از close آزاد می‌شود
  • اگر قبل از established شدن لازم باشد پاسخ failure SOCKS5 فرستاده شود، این کار از مسیر close محافظت‌شده در برابر callback‌های re-entrant انجام می‌شود

این مقدار padding اجازه می‌دهد header مربوط به SOCKS5 UDP به ابتدای پاسخ اضافه شود، بدون آن‌که قرارداد padding WaterWall نقض شود. اگر buffer پاسخ فضای کافی در سمت چپ نداشته باشد، tunnel یک buffer جایگزین می‌گیرد و پیش از افزودن header، payload را در آن کپی می‌کند.

اشتباه‌های رایج​

  • username, password, users, یا accounts را تنظیم نکنید؛ این کلیدهای legacy رد می‌شوند.
  • هم‌زمان auth-client-node-name و no-auth: true نگذارید.
  • UDP را بدون ipv4 یا udp-ipv4 فعال نکنید.
  • از TcpListener -> Socks5Server همراه udp: true استفاده نکنید؛ این ورودی فقط TCP است و provider پویای UDP ندارد.
  • datagram‌های SOCKS UDP را به پورت ثابت تنظیم‌شدهٔ listener نفرستید؛ از BND.PORT پویایی استفاده کنید که UDP ASSOCIATE برمی‌گرداند.
  • بعد از Socks5Server authenticated به‌صورت دستی UserController نگذارید.
  • انتظار پشتیبانی از BIND یا reassembly برای UDP fragmentation نداشته باشید.
  • در حالت UDP فقط بازبودن port کنترل TCP در firewall/NAT کافی نیست؛ relay port پویای برگشتی نیز باید قابل‌دسترسی باشد.

پشتیبانی از splice​

Socks5Server از payload‌های TCP CONNECT مبتنی بر splice پشتیبانی می‌کند. تا پایان مرحلهٔ برقراری TCP، کل ورودی client با sbufEnsureOrdinary() پیش از ورود به parser یا صف ورودی بازگشتی به حافظهٔ عادی تبدیل می‌شود. سپس payload‌های TCP و صف‌های لازم برای حفظ ترتیب می‌توانند buffer اصلی را نگه دارند. پاسخ موفقیت همچنان پیش از بدنهٔ پاسخ backend فرستاده می‌شود و محدودیت‌های موجود برقرارند.

line‌های UDP client و remote خواندن عادی را در هر دو جهت درخواست می‌کنند. سرویس صرفاً UDP این ترجیح را هنگام Init مربوط به TCP control line هم تنظیم می‌کند. در سرویس ترکیبی CONNECT/UDP، ترجیح control line پس از خواندن command مجاز UDP ASSOCIATE تنظیم می‌شود. ترجیح برای تمام عمر همان line باقی می‌ماند و به line‌های مرتبط منتقل نمی‌شود. datagram‌ها همچنان پیش از خواندن header یا قالب‌بندی کاملاً به حافظهٔ عادی تبدیل می‌شوند، چون ورودی تحویل‌داده‌شده یا تبدیل‌شده ممکن است splice باشد.

UserController داخلی قابلیت splice را حفظ می‌کند و AuthenticationClient فقط وابستگی lookup است، نه مسیر payload. فعال‌شدن واقعی splice به chain نهاییِ واجد شرایط و misc.splice وابسته است.

متادیتای node​

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

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

اعلام Est و پاسخ SOCKS دو تعهد جدا هستند. callback تو‌در‌تو در Init یا Est از دادهٔ قدیمی درخواست جلو نمی‌زند. اعلان‌های TCP Pause/Resume و Est حتی در صورت تکرار عبور می‌کنند. پاسخ موفق آماده و صف مرتب دادهٔ برنامه برای Resume منتظر نمی‌مانند. Pause دریافتی پیش از Init سمت بعدی، پس از آماده‌شدن آن سمت منتقل می‌شود؛ Resume پیش از این انتقال آن را لغو می‌کند. نگاشت سیگنال‌ها در مرزهای association UDP مانند قبل باقی می‌ماند. صف‌های لازم برای ترتیب درخواست و پاسخ در هر جهت به ۱ MiB و ۱۰۲۴ buffer محدودند؛ دادهٔ آماده مستقیماً عبور می‌کند.

پس از تحویل پاسخ موفق CONNECT و پایان Init سمت بعدی، انتقال Pause اولیه و تمام کارهای مربوط به ترتیب درخواست و پاسخ، سرور state محلی خود را آزاد می‌کند و روی همان خط TCP به‌صورت دائمی bypass می‌شود. اطلاعات احراز هویت ثبت‌شده روی line حفظ می‌شوند. callbackهای بعدی، از جمله Finish، مستقیماً به nodeهای فعال مجاور می‌رسند. خط‌های کنترل، client و remote مربوط به UDP برای مدیریت association فعال باقی می‌مانند.

پردازش ورودی کنترل در یک محدودهٔ همگام انجام می‌شود و ورودی بازگشتی پس از داده‌های قدیمی‌تر قرار می‌گیرد. مرحلهٔ روش احراز هویت و هویت کاربر پیش از فراخوانی پاسخ ثبت می‌شوند. هر پیام مذاکره حداکثر ۵۱۳ بایت metadata دارد؛ header کامل CONNECT همراه با دادهٔ برنامه مانند دو دریافت جداگانه پذیرفته می‌شود. تنها دادهٔ برنامه‌ای که واقعاً نگه داشته می‌شود از بودجهٔ مشترک صف استفاده می‌کند. ورودی خالی TCP نادیده گرفته می‌شود و پس از UDP ASSOCIATE، باقی‌ماندهٔ ورودی TCP دور ریخته می‌شود، نه اینکه درخواست یا datagram جدید باشد.

دامنه با طول صفر به‌محض دریافت بایت طول نامعتبر است. درخواست TCP با پاسخ «نشانی پشتیبانی نمی‌شود» بسته می‌شود؛ datagram UDP با domain خالی یا آدرس ناقص، بدون بستن ارتباط UDP یا ایجاد اتصال مقصد دور ریخته می‌شود.

اتصال‌های مقصد UDP در هر ارتباط کاربر با نوع نشانی اصلی، بایت‌های دقیق نشانی یا دامنه و پورت تطبیق داده می‌شوند. hash collision و تغییرات بعدی DNS یا مسیریابی، مقصدهای متفاوت را ادغام نمی‌کند و هویت مورد استفاده برای حذف اتصال را تغییر نمی‌دهد. املای دامنه و بزرگی و کوچکی حروف حفظ می‌شود.