VlessClient
VlessClient پیادهسازی سمت client پروتکل plain VLESS v0 در WaterWall است. request header شامل UUID و مقصد نهایی را
مینویسد و سپس داده TCP stream یا UDP datagramها را از node stream-facing بعدی عبور میدهد.
این node فقط بخش پایه VLESS را پیادهسازی میکند؛ TLS، REALITY، XTLS Vision، flow addon، mux، XUDP، WebSocket، gRPC و wrapperهای transport باید با nodeهای دیگری ساخته شوند.
جایگاه رایج
VLESS client برای TCP:
TcpListener -> VlessClient -> TlsClient -> TcpConnector
VLESS client برای UDP:
UdpListener -> VlessClient -> TlsClient -> TcpConnector
مقصد پویا از proxy یا router:
Socks5Server -> VlessClient -> TlsClient -> TcpConnector
SniffRouter -> VlessClient -> TlsClient -> TcpConnector
VlessClient خودش TLS ایجاد نمیکند. در deployment معمول VLESS، TlsClient را بعد از آن قرار دهید و TcpConnector نهایی
را به address و port سرور VLESS وصل کنید. مقصد واقعی سرویس در request header مربوط به VLESS نوشته میشود.
نمونه TCP
[
{
"name": "vless-client",
"type": "VlessClient",
"settings": {
"uuid": "5783a3e7-e373-51cd-8642-c83782b807c5",
"address": "example.com",
"port": 443,
"protocol": "tcp",
"domain-strategy": "do-not-resolve-domains",
"verbose": false
},
"next": "tls-client"
},
{
"name": "tls-client",
"type": "TlsClient",
"settings": {
"sni": "vless.example.net",
"verify": true
},
"next": "server-out"
},
{
"name": "server-out",
"type": "TcpConnector",
"settings": {
"address": "vless.example.net",
"port": 443,
"nodelay": true
}
}
]
نمونه dynamic destination
{
"name": "vless-client",
"type": "VlessClient",
"settings": {
"id": "5783a3e7-e373-51cd-8642-c83782b807c5",
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "dest_context->protocol"
},
"next": "tls-client"
}
id و user-id aliasهای قابل قبول برای uuid هستند.
نمونه UDP
{
"name": "vless-client-udp",
"type": "VlessClient",
"settings": {
"uuid": "5783a3e7-e373-51cd-8642-c83782b807c5",
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "udp"
},
"next": "tls-client"
}
در حالت UDP، node قبلی همچنان با UDP کار میکند. VlessClient برای آن line یک TCP carrier line داخلی میسازد، درخواست
VLESS UDP را روی carrier میفرستد و هر UDP payload را پس از یک length دو بایتی big-endian قرار میدهد.
فیلدهای لازم
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام یکتای node داخل config. |
type | string | باید دقیقاً "VlessClient" باشد. |
settings | object | تنظیمات غیرخالی VLESS client. |
next | string | node stream-facing که بایتهای مربوط به VLESS را حمل میکند؛ در استفاده معمول لازم است. |
فیلدهای لازم داخل settings:
| فیلد | نامهای قابل قبول | توضیح |
|---|---|---|
| UUID | uuid, id, user-id | user ID در VLESS؛ میتواند به فرمت dashed در RFC4122 یا hex فشرده ۳۲ کاراکتری باشد و بهشکل ۱۶ byte خام ارسال میشود. |
| target address | target-address, address, target | host یا IP مقصد نهایی در درخواست VLESS، یا "dest_context->address". |
| target port | port | port مقصد نهایی، یا "dest_context->port"؛ مقدار عددی باید در بازه 1..65535 باشد. |
UUID با byte order شبکه در RFC4122 سریال میشود؛ نه بهشکل ASCII و نه با memory layout مربوط به GUID پلتفرم.
تنظیمات اختیاری
| فیلد | پیشفرض | توضیح |
|---|---|---|
protocol | "tcp" | انتخاب VLESS command. نام proto هم پذیرفته میشود. |
first-payload-timeout-ms | 400 | مهلت به میلیثانیه صحیح در بازه 0 تا 4,294,967,295؛ صفر انتظار عمدی را غیرفعال میکند. |
verbose | false | جزئیات بیشتری از tunnel را log میکند. |
domain-strategy | "do-not-resolve-domains" | مشخص میکند domain مقصد پیش از نوشتن درخواست بهصورت محلی resolve شود یا نه. |
مقدارهای پشتیبانیشده برای protocol:
| مقدار | رفتار |
|---|---|
"tcp" یا "connect" | VLESS TCP command 0x01 ارسال میشود. |
"udp" یا "udp-associate" | VLESS UDP command 0x02 ارسال میشود و UDP datagramها روی TCP carrier داخلی حمل میشوند. |
"dest_context->protocol" یا "line->dest_ctx->protocol" | protocol از destination context ورودی گرفته میشود. TCP دقیق command 0x01 و UDP دقیق command 0x02 را انتخاب میکند. |
اگر با انتخاب dest_context->protocol، protocol ورودی وجود نداشته باشد، مبهم باشد یا TCP/UDP نباشد، client یک warning در
log مینویسد و TCP را انتخاب میکند.
Domain Strategy
domain-strategy تعیین میکند آدرس نهایی در درخواست VLESS چگونه نوشته شود.
| مقدار | رفتار |
|---|---|
"do-not-resolve-domains" | نام domain بدون تغییر در درخواست میماند تا سمت VLESS server آن را resolve کند. |
"resolve-domains-and-accept-dns-returned-order" | resolve محلی انجام میشود و نخستین نتیجه قابل استفاده انتخاب میشود. |
"resolve-domains-and-prefer-ipv4" | پس از resolve محلی، IPv4 در اولویت است و در صورت نیاز IPv6 استفاده میشود. |
"resolve-domains-and-prefer-ipv6" | پس از resolve محلی، IPv6 در اولویت است و در صورت نیاز IPv4 استفاده میشود. |
"resolve-domains-and-use-only-ipv4" | فقط IPv4 قبول میشود. اگر DNS پاسخ IPv4 ندهد line بسته میشود. |
"resolve-domains-and-use-only-ipv6" | فقط IPv6 قبول میشود. اگر DNS پاسخ IPv6 ندهد line بسته میشود. |
"resolve-domains-with-core-settings" | resolve با تنظیمات dns در core.json انجام میشود. |
با فعال بودن resolve محلی، VlessClient یک DomainResolver داخلی میسازد. prepare hook آن پیش از DNS lookup مقصد
تنظیمشده را اعمال میکند. اگر مقصد نهایی VLESS نام domain باشد resolve انجام میشود؛ چه مقصد از JSON آمده باشد و چه از
"dest_context->address". آدرسهای IP بدون تغییر میمانند.
قالب request و Response
request header:
version: 00
uuid: 16 raw bytes
addons len: 00
command: 01 TCP or 02 UDP
destination: port first, then address
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
برای سازگاری بیشتر، client response addon غیرخالی را میپذیرد و یک warning در log ثبت میکند. بایتهای addon نادیده گرفته میشوند و سپس body تحویل داده میشود؛ خود addon پردازش نخواهد شد.
اولین داده و برقراری transport
Init مقصد و node بعدی را آماده میکند و بهتنهایی درخواستی ارسال نمیکند.
اولین payload معتبر میتواند پیش از Est، داخل callback آن یا هنگام Pause برسد.
درخواست کامل و تمام اولین payload در یک buffer معمولی ارسال میشوند؛ در حالت UDP
header یک datagram نیز در همان buffer قرار میگیرد. ورودی عادی با headroom کافی در همان buffer
استفاده میشود؛ در غیر این صورت buffer عادی best-fit ساخته میشود و prefix مقیم و بدنه pipe خصوصی به ترتیب کپی میشوند. TCP خالی درخواست را فعال نمیکند.
datagram خالی UDP با prefix طول 00 00 ارسال میشود و اگر نخستین payload برنامه باشد، درخواست اولیه را فعال میکند.
در TCP مستقیم، تمام رویدادهای transport Est، حتی تکراریها، مستقل از داده پروتکل و Pause به برنامه میرسند.
در UDP، Est مربوط به carrier فقط یک بار به برنامه نگاشت میشود.
اگر درخواست هنوز ارسال نشده باشد، مهلت از اولین Est آغاز میشود.
settings.first-payload-timeout-ms بهطور پیشفرض 400 است و عدد صحیح از 0
تا 4,294,967,295 را میپذیرد؛ نوعها و مقدارهای دیگر نامعتبرند. صفر انتظار عمدی
را غیرفعال میکند. زمان Pause نیز محاسبه میشود. در TCP مستقیم، رسیدن موعد حتی پس از Pause
یک درخواست بدون payload ارسال میکند. تمام اعلانهای Pause/Resume، حتی تکراریها، شفاف عبور
میکنند و وضعیت Pause گیرنده نگهداری نمیشود. در carrier متعلق به client برای UDP، این node
سر همان line است و کنترل تولید خروجی را حفظ میکند: اگر موعد درخواست بدون payload هنگام
Pause آن carrier برسد، ارسال تا Resume منتظر میماند. رسیدن payload معتبر پیش از ارسال درخواست،
timer را لغو کرده و خروجی ترکیبی را ارسال میکند. Est یا Resume تکراری درخواست را دوباره ارسال
نمیکنند و مهلت را از نو آغاز نمیکنند. Finish نیز timer را لغو میکند.
قرار گرفتن داده در یک buffer معمولی به معنی ارسال آن با یک syscall، یک packet TCP یا یک TLS record نیست.
مسیر مستقیم TCP و بستهبندی هر UDP در upstream، صف خروجی برای Est یا Resume ندارند. در پاسخ VLESS نسخه صفر و تمام addonها، حداکثر 257 بایت metadata، مصرف و اعتبارسنجی میشوند. addon غیرخالی با هشدار رد میشود. ارسال خروجی منتظر پاسخ نیست.
پس از ارسال درخواست TCP، مصرف کامل header پاسخ و تخلیه ورودی نگهداشتهشده و callbackهای تو در تو، node وضعیت محلی line را نابود میکند و برای آن line بهطور دائمی bypass میشود. callbackهای بعدی مستقیماً بین همسایهها عبور میکنند. تکمیل فقط درخواست یا فقط پاسخ کافی نیست.
line مربوط به application در UDP همچنان borrowed است و client یک carrier معمولی TCP وابسته روی همان worker ایجاد میکند. Est و Pause/Resume و Finish به association درست نگاشت میشوند. درخواست مقصد ثابت را حمل میکند و هر فریم یک طول دوبایتی big-endian دارد. decoder تمام datagramهای کامل ورودی پذیرفتهشده را حتی پس از Pause تحویل میدهد. ورودی callback تو در تو پشت ورودی قدیمی در FIFO parser قرار میگیرد؛ تنها قطعه ناقص یا وضعیت لازم برای ترتیب باقی میماند. Finish، ورودی خراب یا رد گیرنده، تحویل را متوقف و association را میبندد. این node فقط carrier متعلق به خودش را نابود میکند؛ مالک برنامه در shutdown همین carrier وابسته را نیز تخلیه میکند.
Splice و محدودیت نگهداری
اولین درخواست و payload معمولی هستند؛ خود درخواست حداکثر 278 بایت است. TCP بعدی هویت buffer معمولی یا splice را حفظ میکند. UDP از padding اعلامشده 280 بایتی استفاده میکند؛ هدر هر datagram همچنان حداکثر 2 بایت است. parser فقط هدر لازم را میخواند و حرکت دقیق بدنه، مرز datagram و padding کامل مسیر بعدی را حفظ میکند. کمبود padding یا ظرفیت pipe با fallback کامل معمولی مدیریت میشود.
| ورودی نگهداریشده parser | سقف منطقی شامل برابری |
|---|---|
| بدنه TCP فقط برای ترتیب ورودی تو در تو | 2,097,152 بایت، برابر 2 MiB |
| تکمیل پاسخ TCP با head فعال و metadata | 2,097,409 بایت، برابر 2 MiB بهعلاوه 257 بایت |
| تکمیل ورودی UDP با head فعال و هدرها | 2,162,689 بایت، برابر 2 MiB بهعلاوه 65,537 بایت |
| بدنه UDP | از 0 تا 65,535 بایت |
برابری مجاز است؛ سرریز یا رد صف parser همان association را میبندد. صف خروجی
decodeشده و محدودیت 1,024 رکورد برای batch همزمان وجود ندارد. تعداد قطعههای
ورودی سهمیه خروجی را مصرف نمیکند. این حدود از اندازه pool، پروفایل RAM و ظرفیت
pipe مستقلاند. datagram محلی بیشازحد بزرگ حذف میشود و فریم دریافتی خراب association
را میبندد. length دریافتی 0 prefix دو بایتی خود را مصرف میکند و دقیقاً یک datagram خالی UDP
تحویل میدهد. association باز میماند و parser سپس frame بعدی را بهترتیب پردازش میکند.
این رفتار با پیادهسازی base VLESS در Xray تفاوت دارد: writerها datagram خالی را حذف میکنند و reader از frame با طول صفر عبور میکند.
تحویل datagram خالی به پشتیبانی peer نیاز دارد؛ framing داده غیرخالی تغییری نمیکند.
datagram خالی در لایه UDP معتبر است (RFC 8085 §5).
splice نیازمند misc.splice، build مناسب و پشتیبانی تمام nodeهای chain است.
DomainResolver پشتیبانی میکند؛ TlsClient همچنان splice را برای کل chain
غیرفعال میکند. در استقرار معمول TLS را نگه دارید؛ این قابلیت تضمین zero-copy
یا افزایش سرعت اندازهگیریشده نیست.
رفتار Finish
VlessClient پیش از فرستادن Finish واقعی، line state خود را از بین میبرد. برای lineهای معمولی بیرونی نیز
lineDestroy() را فراخوانی نمیکند.
در حالت UDP، مالک carrier line داخلیای است که خودش ساخته است. بسته شدن app line یا carrier line باعث بسته شدن سمت مقابل و از بین رفتن امن carrier line میشود.
padding
VlessClient برای بزرگترین درخواست با اندازه ۲۷۸ بایت و prefix دو بایتی طول اولین datagram UDP
مقدار زیر را اعلام میکند:
required_padding_left = 280
قابلیتهای پشتیبانینشده
این پیادهسازی موارد زیر را پشتیبانی نمیکند:
- ساخت TLS یا REALITY داخل node VLESS
- XTLS Vision یا flow addons
- VLESS mux
- reverse
- XUDP
- WebSocket، HTTP، gRPC یا transport wrapperهای دیگر
برای این لایهها از nodeهای جدا مثل TlsClient، HttpClient، MuxClient یا TcpConnector استفاده کنید.
اشتباههای رایج
- انتظار نداشته باشید
VlessClientخودش به VLESS server وصل شود؛ بعد از آن stream transport بگذارید. - در deployment عمومی VLESS،
TlsClientرا حذف نکنید مگر اینکه عمداً plain VLESS را روی wire بخواهید. - address سرور VLESS را با target address نهایی اشتباه نگیرید.
TcpConnectorبه server وصل میشود؛ VLESS request header مقصد نهایی را حمل میکند. - UDP mode را با next node غیر stream-facing استفاده نکنید.
- به featureهای پشتیبانینشده مثل Vision، flow، mux یا 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 | 280 بایت |