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 در downstream | 2,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 دیگری سپرد.
فیلدهای لازم
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام یکتای node داخل config. |
type | string | باید دقیقاً "VlessServer" باشد. |
settings | object | تنظیمات غیرخالی VLESS server. |
next | string | لازم است؛ ترافیک پذیرفتهشده VLESS به این node فرستاده میشود. |
باید دقیقاً یکی از دو روش authentication را انتخاب کنید.
تنظیمات authentication محلی
برای authentication محلی، auth-client-node-name را حذف کنید و حداقل یک UUID تنظیم کنید.
| فیلد | نوع | توضیح |
|---|---|---|
uuid | string | یک UUID. |
id | string | alias برای uuid. همزمان uuid و id را استفاده نکنید. |
uuids | array of strings | چند UUID. |
users | array | UUID string یا object با یکی از uuid، id یا user-id. object میتواند username داشته باشد. |
clients | array | alias برای users. همزمان users و clients را استفاده نکنید. |
UUID میتواند به فرمت dashed در RFC4122 یا hex فشرده ۳۲ کاراکتری باشد. UUID تکراری خطایی fatal در config است، حتی اگر usernameهای متفاوتی داشته باشد.
تنظیمات Database authentication
| فیلد | نوع | توضیح |
|---|---|---|
auth-client-node-name | string | نام یک node AuthenticationClient موجود در همان config. |
در database mode، uuid، id، uuids، users و clients را تنظیم نکنید.
تنظیمات اختیاری
| فیلد | پیشفرض | توضیح |
|---|---|---|
connect | true | VLESS TCP command 0x01 را فعال میکند. |
udp | true | VLESS UDP command 0x02 را فعال میکند. |
verbose | false | جزئیات بیشتری از رد اتصال و diagnostics را log میکند. |
fallback-node-name | تنظیم نشده | branch جایگزین برای probeهای نامعتبر و احراز هویتنشده. نامهای fallback-node و fallback هم پذیرفته میشوند. |
fallback-intentional-delay-ms | 7 | تأخیر payloadهای upstream که به fallback میروند؛ در delay غیرصفر jitter اعمال میشود. در 0، fallback فعال که FIFO قدیمی یا drain زمانبندیشده ندارد payload را inline میگیرد؛ بایتهای نگهداشتهشده ترتیب FIFO را حفظ میکنند و drain زمانبندیشده در موعد خود بدون انتظار برای Resume اجرا میشود. |
fallback-intentional-delay-jitter-ms | 1 | jitter تصادفی برای زمانبندی 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:
- UUID را بررسی میکند
- destination درخواستی را میخواند
- destination را داخل
line->routing_context.dest_ctxمینویسد - node
nextرا مقداردهی اولیه میکند - بایتهای TCP body را که همراه request header رسیدهاند میفرستد
- 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 پیش ازFinishfallback دریافت میکند، حتی هنگام 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 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 | 4 بایت |