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 نداشته باشد، راهاندازی شکست میخورد.
برای استقرار عادی با 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.
فیلدهای اجباری
| فیلد | نوع | توضیح |
|---|---|---|
settings | object | اجباری است و نباید خالی باشد. |
settings.auth-client-node-name | string | اجباری است مگر اینکه no-auth صریحاً true باشد. باید به یک node AuthenticationClient موجود اشاره کند و نمیتواند خود همین Socks5Server باشد. |
settings.no-auth | boolean | فقط وقتی لازم است که auth client ندارید؛ برای فعال شدن حالت بدون احراز هویت باید true باشد. |
settings.ipv4 / settings.udp-ipv4 | string | وقتی udp فعال است اجباری است. آدرس IPv4 قابلدسترسی که در BND.ADDR پاسخ UDP ASSOCIATE اعلام میشود؛ این مقدار آدرس bind مربوط به listener نیست. |
next | string | اجباری است و به مسیر outbound واقعی اشاره میکند. |
حداقل یکی از connect یا udp باید فعال باشد.
وقتی udp فعال است، مسیر قبلی نهاییشده باید یک provider پویای UDP ارائه دهد.
در توپولوژی عادی با nodeهای داخلی، یعنی سرویس را با TcpUdpListener بهجای
TcpListener آغاز کنید.
فیلدهای اختیاری
| فیلد | پیشفرض | توضیح |
|---|---|---|
settings.connect | true | فعالسازی CONNECT. |
settings.udp | false | فعالسازی UDP ASSOCIATE و پردازش datagramهای UDP. |
settings.verbose | false | log بیشتر، از جمله دلیل رد شدن auth. |
settings.sweep-interval-ms | 1000 | به 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 تبدیل میشود:
- مقصد درخواستی SOCKS داخل
line->dest_ctxکپی میشود. - protocol مقصد TCP تنظیم میشود.
Initبه سمت node بعدی ارسال میشود.- دادهٔ upstream پس از احراز هویت و Init سمت بعدی، حتی پیش از Est، قابل ارسال است.
- هر
Estاز سمت بعدی، حتی اعلان تکراری، بلافاصله به node قبلی فرستاده میشود؛ پاسخ موفق دوباره تولید نمیشود. - پاسخ موفق 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 برسد:
- association فعال با استفاده از هویت opaque مربوط به dynamic endpoint پیدا میشود.
- header مربوط به SOCKS5 UDP اعتبارسنجی میشود.
- اگر
FRAG != 0باشد، datagram محافظهکارانه نادیده گرفته میشود. - مقصد remote از هدر خوانده میشود.
- هدر SOCKS5 UDP حذف و payload با destination context درخواستی به سمت upstream ارسال میشود.
وقتی پاسخ از node بعدی برگردد:
- payload در یک header پاسخ SOCKS5 UDP قرار میگیرد.
- source address داخل header، endpoint راه دور مرتبط با همان پاسخ را مشخص میکند.
- 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برمیگرداند. - بعد از
Socks5Serverauthenticated بهصورت دستی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 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 و پاسخ 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 یا مسیریابی، مقصدهای متفاوت را ادغام نمیکند و هویت مورد استفاده برای حذف اتصال را تغییر نمیدهد. املای دامنه و بزرگی و کوچکی حروف حفظ میشود.