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

VlessServer

VlessServer پیاده‌سازی سمت server پروتکل plain VLESS v0 در WaterWall است. request header را از node stream-facing قبلی می‌خواند، UUID خام ۱۶ بایتی را بررسی می‌کند، مقصد TCP یا UDP درخواستی را بیرون می‌کشد و ترافیک پذیرفته‌شده را به next می‌فرستد.

این node فقط بخش پایه پروتکل VLESS را پیاده‌سازی می‌کند؛ TLS، REALITY، XTLS Vision، mux، reverse، XUDP و wrapper‌های transport باید با node‌های دیگری ساخته شوند.

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

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

پشتیبانی splice و محدودیت نگهداری​

VlessServer در هر دو جهت payload عادی و splice را می‌پذیرد. فعال شدن splice به Linux پشتیبانی‌شده، فعال بودن misc.splice و قابلیت تمام node‌های chain نهایی وابسته است؛ UserController، helper‌های connector و DomainResolver و تمام شاخه‌های fallback نیز در این بررسی هستند. TLS و fallback فقط عادی همچنان کل chain را مسدود می‌کنند.

parser فقط metadata لازم را در cache حداکثر 278 بایتی می‌خواند. طول منطقی نخستین payload باید version و تمام 16 بایت UUID را در بر بگیرد، حتی اگر بخشی در pipe خصوصی باشد. کمتر از 17 بایت، از جمله ورودی خالی، به fallback یا رد شدن می‌انجامد. authentication موفق cache می‌شود؛ addons غیرصفر و درخواست نامعتبر پس از authentication اتصال محافظت‌شده را می‌بندند. metadata پذیرفته‌شده پیش از محاسبه بودجه body حذف می‌شود.

نخستین پاسخ غیرخالی TCP یا نخستین پاسخ frameشده UDP، حتی datagram خالی، همراه با header پاسخ 00 00 در یک buffer عادی ارسال می‌شود. ورودی عادی با headroom کافی در همان buffer استفاده می‌شود؛ ورودی splice یا headroom ناکافی به یک buffer عادی best-fit منتقل می‌شود. پاسخ‌های بعدی TCP تا حد امکان opaque باقی می‌مانند. UDP یک مقصد ثابت از درخواست اولیه دارد؛ هر frame شامل طول دو بایتی و body با اندازه 0 تا 65,535 بایت است. شکست ساخت pipe یا فشار هنگام انتقال، fallback کامل به حافظه عادی دارد؛ zero-copy برای همه frame‌ها تضمین نمی‌شود.

داده نگه‌داشته‌شدهسقف با پذیرش مقدار مساوی
Init/reentry و backlog مربوط به fallback در upstream، مشترک2,097,152 بایت منطقی و 1,024 buffer
صف response-header/reentry در downstream2,097,152 بایت منطقی و 1,024 buffer
parser درخواست/UDP، شامل metadata در cache و ورودی فعال2,162,689 بایت منطقی

buffer خروجی خالی نیز یک entry مصرف می‌کند؛ fragment‌های یک UDP frame ناقص سقف 1,024 entry خروجی را ندارند. overflow یا رد پذیرش صف، association مربوط را می‌بندد و داده محلی را آزاد می‌کند. این اعداد سقف RSS، حافظه kernel pipe یا اندازه TCP آماده برای تحویل مستقیم نیستند. fallback با تأخیر صفر و بدون backlog قدیمی می‌تواند payload پذیرفته‌شده را همگام حتی پس از Pause کامل کند؛ تخلیه مستقل backlog در موعد خود منتظر Resume نمی‌ماند. هنگام Finish فقط یک batch نهایی خارج از branch Init مجاز است، حتی پس از Pause؛ درون Init داده محلی آزاد می‌شود.

جایگاه رایج​

VLESS server عادی:

TcpListener -> TlsServer -> VlessServer -> TcpUdpConnector

خروجی فقط TCP:

TcpListener -> TlsServer -> VlessServer -> TcpConnector

خروجی با پشتیبانی UDP:

TcpListener -> TlsServer -> VlessServer -> TcpUdpConnector

VlessServer خودش TLS را terminate نمی‌کند. در deployment عمومی VLESS، TlsServer را پیش از آن قرار دهید؛ در غیر این صورت UUID و metadata مقصد روی wire دیده می‌شوند.

نمونه Minimal​

[
{
"name": "tls-in",
"type": "TlsServer",
"settings": {
"cert-file": "/etc/waterwall/fullchain.pem",
"key-file": "/etc/waterwall/privkey.pem"
},
"next": "vless-server"
},
{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"uuid": "5783a3e7-e373-51cd-8642-c83782b807c5",
"connect": true,
"udp": true,
"verbose": false
},
"next": "outbound"
},
{
"name": "outbound",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}
]

چند User محلی​

{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"users": [
"5783a3e7-e373-51cd-8642-c83782b807c5",
{
"username": "alice",
"id": "11111111-2222-3333-4444-555555555555"
}
],
"connect": true,
"udp": true
},
"next": "outbound"
}

در حالت allowlist محلی، اگر object فیلد username داشته باشد همان نام روی line ثبت می‌شود. UUID canonical با حروف کوچک هم به‌عنوان password مربوط به line ذخیره می‌شود تا rule‌های Router حتی بدون database کاربران بتوانند credential را بررسی کنند.

حالت AuthenticationClient​

{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"auth-client-node-name": "auth-client",
"connect": true,
"udp": true
},
"next": "outbound"
}

در این حالت، UUID روی wire به فرمت canonical، dashed و با حروف کوچک تبدیل می‌شود و سپس به‌عنوان password کاربر از طریق AuthenticationClient بررسی می‌شود.

نمونه user در database:

{
"id": 2001,
"name": "vless-user",
"password": "5783a3e7-e373-51cd-8642-c83782b807c5",
"enabled": true
}

با تنظیم auth-client-node-name دیگر نمی‌توانید UUID محلی تعریف کنید. VlessServer خودش یک UserController داخلی پیش از outbound قرار می‌دهد تا accounting و مسیریابی مبتنی بر user درست کار کنند.

نمونه fallback​

{
"name": "vless-server",
"type": "VlessServer",
"settings": {
"uuid": "5783a3e7-e373-51cd-8642-c83782b807c5",
"fallback-node-name": "fallback-service",
"fallback-intentional-delay-ms": 7,
"fallback-intentional-delay-jitter-ms": 1,
"connect": true,
"udp": true
},
"next": "outbound"
}

fallback برای مقاومت در برابر active probing است. به‌جای بستن فوری ترافیک نامعتبر یا احراز هویت‌نشده، می‌توان آن را به node دیگری سپرد.

فیلدهای لازم​

فیلدنوعتوضیح
namestringنام یکتای node داخل config.
typestringباید دقیقاً "VlessServer" باشد.
settingsobjectتنظیمات غیرخالی VLESS server.
nextstringلازم است؛ ترافیک پذیرفته‌شده VLESS به این node فرستاده می‌شود.

باید دقیقاً یکی از دو روش authentication را انتخاب کنید.

تنظیمات authentication محلی​

برای authentication محلی، auth-client-node-name را حذف کنید و حداقل یک UUID تنظیم کنید.

فیلدنوعتوضیح
uuidstringیک UUID.
idstringalias برای uuid. هم‌زمان uuid و id را استفاده نکنید.
uuidsarray of stringsچند UUID.
usersarrayUUID string یا object با یکی از uuid، id یا user-id. object می‌تواند username داشته باشد.
clientsarrayalias برای users. هم‌زمان users و clients را استفاده نکنید.

UUID می‌تواند به فرمت dashed در RFC4122 یا hex فشرده ۳۲ کاراکتری باشد. UUID تکراری خطایی fatal در config است، حتی اگر username‌های متفاوتی داشته باشد.

تنظیمات Database authentication​

فیلدنوعتوضیح
auth-client-node-namestringنام یک node AuthenticationClient موجود در همان config.

در database mode، uuid، id، uuids، users و clients را تنظیم نکنید.

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

فیلدپیش‌فرضتوضیح
connecttrueVLESS TCP command 0x01 را فعال می‌کند.
udptrueVLESS UDP command 0x02 را فعال می‌کند.
verbosefalseجزئیات بیشتری از رد اتصال و diagnostics را log می‌کند.
fallback-node-nameتنظیم نشدهbranch جایگزین برای probe‌های نامعتبر و احراز هویت‌نشده. نام‌های fallback-node و fallback هم پذیرفته می‌شوند.
fallback-intentional-delay-ms7تأخیر payload‌های upstream که به fallback می‌روند؛ در delay غیرصفر jitter اعمال می‌شود. در 0، fallback فعال که FIFO قدیمی یا drain زمان‌بندی‌شده ندارد payload را inline می‌گیرد؛ بایت‌های نگه‌داشته‌شده ترتیب FIFO را حفظ می‌کنند و drain زمان‌بندی‌شده در موعد خود بدون انتظار برای Resume اجرا می‌شود.
fallback-intentional-delay-jitter-ms1jitter تصادفی برای زمان‌بندی payload‌های تأخیردار fallback؛ با delay صفر نادیده گرفته می‌شود.

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

قالب request و Response​

request پذیرفته‌شده:

version:      1 byte, must be 00
user id: 16 raw UUID bytes
addons len: 1 byte, must be 00
command: 01 TCP or 02 UDP
destination: port first, then address
body: TCP raw stream or UDP length-prefixed packets

destination format:

port:          2 bytes big-endian
address type: 01 IPv4, 02 domain, 03 IPv6
address body: IPv4 4 bytes, domain length + bytes, or IPv6 16 bytes

response header:

00 00

در مسیرهای TCP و fallback روی همان line، تمام رویدادهای transport Est، حتی تکراری‌ها، منتقل می‌شوند و به response header یا Pause وابسته نیستند. Est مربوط به backend متعلق به node در UDP فقط یک بار به association برنامه نگاشت می‌شود. header پاسخ 00 00 دقیقاً یک بار، همراه تمام نخستین پاسخ غیرخالی TCP یا نخستین پاسخ frameشده UDP در یک buffer عادی ارسال می‌شود؛ این قاعده پاسخ معتبر پیش از Est را نیز شامل می‌شود. پاسخ خالی UDP معتبر است: نخستین پاسخ ترکیبی آن 00 00 00 00 است که header پاسخ و طول صفر datagram را در بر می‌گیرد. Est، Resume و پاسخ خالی TCP هیچ header مستقلی ارسال نمی‌کنند و timer برای ارسال header تنها وجود ندارد. اگر اتصال بدون پاسخ backend بسته شود، هیچ بایت پاسخ VLESS ارسال نمی‌شود. پاسخ‌های بعدی مسیر عادی با پشتیبانی splice را حفظ می‌کنند. این کار write مستقل header را حذف می‌کند، اما مرز packet‌های TCP یا record‌های TLS را تضمین نمی‌کند.

مسیرهای TCP و fallback همه اعلان‌های Pause/Resume، حتی تکراری‌ها، را عبور می‌دهند. صف ترتیب پاسخ TCP و replay آماده fallback بدون انتظار برای Resume تخلیه می‌شوند. associationهای UDP کنترل تولیدکننده را حفظ می‌کنند: تخلیه مستقل صف پاسخ آن‌ها هنگام Pause خروجی کلاینت منتظر می‌ماند، ولی پاسخ پذیرفته‌شده می‌تواند همگام کامل شود. reentry از پاسخ یا Est ترتیب پاسخ‌های قدیمی‌تر را تغییر نمی‌دهد. صف ضروری پاسخ سقف ۲ MiB و ۱٬۰۲۴ buffer دارد؛ پذیرش تراکنشی است و سرریز همان association را می‌بندد. Pause ثبت‌شده پیش از Init پس از مقداردهی شاخه منتخب و هر backend UDP تازه یا بازسازی‌شده دوباره به آن منتقل می‌شود. پیش از انتخاب شاخه، Resume آخرین Pause ثبت‌شده را لغو می‌کند. ورودی application داخل Init یا replay شاخه نیز در صف موقت با همین سقف‌ها پشت ورودی اصلی می‌ماند. احراز هویت، انتخاب fallback و مالکیت backend UDP حفظ می‌شوند.

در TCP احراز هویت‌شده، پس از تحویل اولین header و body پاسخ و پایان مقداردهی اولیه، تخلیه ورودی و پاسخ‌های نگه‌داشته‌شده، node برای آن line به‌طور دائمی bypass می‌شود. وضعیت محلی line پیش از bypass نابود می‌شود. UDP و fallback برای framing، پاک‌سازی lineهای متعلق به node و مسیریابی branch فعال می‌مانند.

رفتار runtime در TCP​

برای command 0x01، server:

  1. UUID را بررسی می‌کند
  2. destination درخواستی را می‌خواند
  3. destination را داخل line->routing_context.dest_ctx می‌نویسد
  4. node next را مقداردهی اولیه می‌کند
  5. بایت‌های TCP body را که همراه request header رسیده‌اند می‌فرستد
  6. response header را همراه نخستین پاسخ غیرخالی backend در یک buffer عادی می‌فرستد

node next معمولاً TcpConnector، TcpUdpConnector، Router یا node‌ای است که destination context را می‌شناسد.

رفتار runtime در UDP​

برای command 0x02، destination داخل VLESS request اولیه برای همان VLESS line ثابت می‌ماند. frame‌های UDP جداگانه آدرس per-packet ندارند.

UDP frame‌ها:

length:   2 bytes big-endian
payload: length bytes

VlessServer برای destination مورد نظر یک backend UDP line داخلی می‌سازد. frame‌های ورودی VLESS UDP پس از خواندن length prefix به آن line می‌روند. پاسخ backend نیز با همان prefix دو بایتی length بسته‌بندی می‌شود و در جهت downstream به client VLESS برمی‌گردد.

length دریافتی 0 prefix دو بایتی خود را مصرف می‌کند و دقیقاً یک datagram خالی UDP تحویل می‌دهد. association باز می‌ماند و parser سپس frame بعدی را به‌ترتیب پردازش می‌کند. پاسخ خالی backend با prefix طول 00 00 ارسال می‌شود؛ پاسخ محلی بیش‌ازحد بزرگ بدون بستن backend حذف می‌شود. این رفتار با پیاده‌سازی base VLESS در Xray تفاوت دارد: writerها datagram خالی را حذف می‌کنند و reader از frame با طول صفر عبور می‌کند. تحویل datagram خالی به پشتیبانی peer نیاز دارد؛ framing داده غیرخالی تغییری نمی‌کند. datagram خالی در لایه UDP معتبر است (RFC 8085 §5).

ورودی parser در UDP، شامل metadata و ورودی فعال، تا 2,162,689 بایت محدود است. prefix دو بایتی length یا body ناقص تا رسیدن ورودی بعدی نگه داشته می‌شود.

رفتار fallback​

fallback فقط پیش از پذیرفته شدن UUID کاربرد دارد. پس از authentication موفق، addon نامعتبر، command پشتیبانی‌نشده، مقصد خراب یا UDP frame نادرست باعث بسته شدن VLESS line می‌شود و بایت‌های احراز هویت‌شده دوباره به fallback داده نمی‌شوند.

fallback ممکن است در این حالت‌ها انتخاب شود:

  • version byte نامعتبر
  • UUID که در اولین upstream payload split شده باشد
  • UUID lookup ناموفق
  • نبود دسترسی به cache مجاز AuthenticationClient

وقتی fallback تنظیم شده باشد:

  • fallback branch بلافاصله Init می‌گیرد
  • بایت‌های ابتدایی ذخیره‌شده و payload‌های بعدی upstream به fallback فرستاده می‌شوند
  • payload‌های upstream در fallback در عملیات عادی و delay غیرصفر به‌اندازه fallback-intentional-delay-ms به‌علاوه jitter تأخیر دارند
  • در delay صفر، fallback فعال که FIFO قدیمی یا drain زمان‌بندی‌شده ندارد payload را inline می‌گیرد؛ بایت‌های نگه‌داشته‌شده ترتیب FIFO را حفظ می‌کنند و drain زمان‌بندی‌شده در موعد خود بدون انتظار برای Resume اجرا می‌شود
  • FIFO تأخیردار fallback با ورودی Init/reentry یک بودجه مشترک 2 MiB و 1,024 buffer دارد
  • در upstream Finish، مسیر مقداردهی‌شده یک batch پذیرفته‌شده را هم‌زمان و به‌ترتیب FIFO پیش از Finish fallback دریافت می‌کند، حتی هنگام Pause؛ مسیر هنوز داخل Init، batch محلی را دور می‌ریزد و بسته می‌شود
  • پاسخ‌های downstream در fallback عمداً به تأخیر نمی‌افتند

delay باقی‌مانده پس از Finish زنده نگه داشته نمی‌شود. کیفیت fallback بخشی از fingerprint عمومی سرویس است. fallback خراب، خطای generic، بسته شدن فوری یا تفاوت در محتوا و timing می‌تواند deployment را لو بدهد. delay و jitter را با رفتار سرویس عمومی خود تنظیم کنید؛ این‌ها فقط ریسک را کم می‌کنند و تضمینی برای پنهان ماندن نیستند.

قانون First payload برای authentication​

credential کامل VLESS، یعنی byte نسخه به‌علاوه UUID خام ۱۶ بایتی، باید در نخستین callback مربوط به upstream payload حاضر باشد.

اگر نخستین payload credential کامل را نداشته باشد، line مانند یک probe نامعتبر و احراز هویت‌نشده در نظر گرفته می‌شود. در صورت وجود fallback، بایت‌ها به آن می‌روند وگرنه line بسته می‌شود. پس از دریافت کامل UUID، فیلدهای باقی‌مانده می‌توانند در callback‌های بعدی برسند.

رفتار Finish​

VlessServer پیش از فرستادن Finish واقعی، line state خود را از بین می‌برد.

برای TCP و fallback، Finish در صورت نیاز به branch انتخاب‌شده فرستاده می‌شود. در UDP، node مالک backend UDP line داخلی‌ای است که خودش ساخته و با بسته شدن هر یک از دو سمت، آن را به‌شکل امن می‌بندد.

padding​

VlessServer یک بار header پاسخ دو بایتی و برای هر پاسخ UDP یک prefix دو بایتی طول اضافه می‌کند. نخستین پاسخ UDP به هر دو نیاز دارد، بنابراین مقدار زیر را اعلام می‌کند:

required_padding_left = 4

قابلیت‌های پشتیبانی‌نشده​

پیاده‌سازی فعلی موارد زیر را نمی‌پذیرد:

  • request addons length غیرصفر
  • XTLS Vision flow addons
  • mux command 0x03
  • reverse command 0x04
  • command‌های ناشناخته
  • XUDP و رفتار پیشرفته UDP/mux
  • VLESS روی transport‌های non-stream

probe‌های invalid و unauthenticated ممکن است به fallback بروند. protocol data بد بعد از authentication بدون نوشتن response header دیگر close می‌شود.

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

  • VlessServer را بدون TlsServer مستقیماً روی listener عمومی قرار ندهید، مگر اینکه عمداً plain VLESS را روی wire بخواهید.
  • local UUID‌ها را همراه auth-client-node-name تنظیم نکنید.
  • node بعدی را دستی UserController نگذارید؛ database mode خودش یک UserController داخلی اضافه می‌کند.
  • انتظار نداشته باشید UDP frame‌ها برای هر packet مقصد جدید انتخاب کنند؛ VLESS request اولیه UDP target را ثابت می‌کند.
  • به feature‌های پشتیبانی‌نشده مثل Vision، flow، mux، reverse یا XUDP تکیه نکنید.

متادیتای node​

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

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