TrojanClient
TrojanClient پیادهسازی سمت client پروتکل Trojan در WaterWall است. hash مربوط به password، command درخواست و header مقصد
را با فرمت استاندارد Trojan مینویسد و سپس ترافیک application را از node stream-facing بعدی عبور میدهد.
هر دو حالت TCP CONNECT و UDP ASSOCIATE در Trojan پشتیبانی میشوند. خود node TLS ایجاد نمیکند و اتصال به server راه دور
را هم باز نمیکند.
جایگاه رایج
Trojan client برای TCP:
TcpListener -> TrojanClient -> TlsClient -> TcpConnector
Trojan client برای UDP:
UdpListener -> TrojanClient -> TlsClient -> TcpConnector
مقصد پویا از proxy یا router:
Socks5Server -> TrojanClient -> TlsClient -> TcpConnector
SniffRouter -> TrojanClient -> TlsClient -> TcpConnector
TcpConnector معمولاً به address و port سرور Trojan وصل میشود. مقصد واقعی سرویس در درخواست Trojan یا در هر Trojan UDP
packet قرار میگیرد.
نمونه TCP
[
{
"name": "trojan-client",
"type": "TrojanClient",
"settings": {
"password": "secret-password",
"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": "trojan.example.net",
"verify": true
},
"next": "server-out"
},
{
"name": "server-out",
"type": "TcpConnector",
"settings": {
"address": "trojan.example.net",
"port": 443,
"nodelay": true
}
}
]
این request به این شکل ارسال میشود:
hex(SHA224("secret-password")) CRLF
command 01
destination example.com:443
CRLF
نمونه dynamic destination
{
"name": "trojan-client",
"type": "TrojanClient",
"settings": {
"sha224": "7e240de74fb1ed08fa08d38063f6a6a91462a815c15d3f6abf1d7e0b",
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "dest_context->protocol"
},
"next": "tls-client"
}
اگر نمیخواهید password خام در config نوشته شود، از sha224 استفاده کنید. مقدار آن باید digest شانزدهی ۵۶ کاراکتری
SHA224 باشد.
نمونه UDP
{
"name": "trojan-client-udp",
"type": "TrojanClient",
"settings": {
"password": "secret-password",
"address": "dest_context->address",
"port": "dest_context->port",
"protocol": "udp"
},
"next": "tls-client"
}
در حالت UDP، node قبلی همچنان با UDP کار میکند. TrojanClient یک TCP carrier line داخلی میسازد، درخواست Trojan
UDP ASSOCIATE را روی آن میفرستد و هر UDP payload محلی را با framing مخصوص Trojan UDP حمل میکند.
فیلدهای اجباری
فیلدهای top-level:
| field | type | توضیح |
|---|---|---|
name | string | نام دلخواه node؛ باید داخل config یکتا باشد. |
type | string | باید دقیقاً "TrojanClient" باشد. |
settings | object | تنظیمات Trojan client؛ نباید خالی باشد. |
next | string | node stream-facing که بایتهای پروتکل Trojan را حمل میکند؛ در استفاده معمول لازم است. |
فیلدهای اجباری داخل settings:
| field | نامهای قابل قبول | توضیح |
|---|---|---|
| password material | password, pass, sha224, password-sha224, password_sha224 | یا password خام بدهید یا مقدار hex آماده SHA224؛ استفاده همزمان از هر دو مجاز نیست. |
| target address | target-address, address, target | مقصد نهایی host/IP، یا "dest_context->address" / "line->dest_ctx->address". |
| target port | port | پورت مقصد نهایی، یا "dest_context->port" / "line->dest_ctx->port". پورت ثابت باید در بازه 1..65535 باشد. |
password و pass مقدار خام password در Trojan هستند. TrojanClient مقدار SHA224(password) را محاسبه و بهشکل hex
با حروف کوچک ارسال میکند. مقدار aliasهای sha224 باید دقیقاً ۵۶ کاراکتر hexadecimal باشد؛ حروف بزرگ بهطور خودکار به
حروف کوچک تبدیل میشوند.
تنظیمات اختیاری
| field | default | توضیح |
|---|---|---|
protocol | "tcp" | Trojan command را انتخاب میکند. alias آن proto است. |
first-payload-timeout-ms | 400 | مهلت به میلیثانیه صحیح در بازه 0 تا 4,294,967,295؛ صفر انتظار عمدی را غیرفعال میکند. |
verbose | false | جزئیات بیشتری از tunnel را log میکند. |
domain-strategy | "do-not-resolve-domains" | مشخص میکند domain مقصد پیش از نوشتن درخواست بهصورت محلی resolve شود یا نه. |
مقادیر قابل قبول protocol:
| value | رفتار |
|---|---|
"tcp" یا "connect" | Trojan command 0x01 برای TCP CONNECT ارسال میشود. |
"udp" یا "udp-associate" | Trojan command 0x03 برای UDP ASSOCIATE ارسال میشود و UDP datagramها روی carrier داخلی TCP حمل میشوند. |
"dest_context->protocol" یا "line->dest_ctx->protocol" | protocol از destination context ورودی خوانده میشود. TCP دقیق 0x01 و UDP دقیق 0x03 را انتخاب میکند. |
اگر با انتخاب dest_context->protocol، protocol ورودی وجود نداشته باشد، مبهم باشد یا TCP/UDP نباشد، client یک warning در
log مینویسد و TCP را انتخاب میکند.
Domain Strategy
domain-strategy تعیین میکند آدرس نهایی در درخواست Trojan و headerهای UDP packet چگونه نوشته شود.
| value | رفتار |
|---|---|
"do-not-resolve-domains" | نام domain بدون تغییر میماند تا سمت Trojan server آن را resolve کند. |
"resolve-domains-and-accept-dns-returned-order" | resolve محلی انجام میشود و نخستین پاسخ قابل استفاده DNS انتخاب میشود. |
"resolve-domains-and-prefer-ipv4" | پس از resolve محلی، IPv4 در اولویت است و در صورت نیاز IPv6 استفاده میشود. |
"resolve-domains-and-prefer-ipv6" | پس از resolve محلی، IPv6 در اولویت است و در صورت نیاز IPv4 استفاده میشود. |
"resolve-domains-and-use-only-ipv4" | فقط IPv4 پذیرفته میشود؛ نبودن پاسخ IPv4 باعث بسته شدن line است. |
"resolve-domains-and-use-only-ipv6" | فقط IPv6 پذیرفته میشود؛ نبودن پاسخ IPv6 باعث بسته شدن line است. |
"resolve-domains-with-core-settings" | resolve محلی با تنظیمات dns در core.json انجام میشود. |
با فعال بودن resolve محلی، TrojanClient یک DomainResolver داخلی میسازد. prepare hook آن پیش از DNS lookup، مقصد
تنظیمشده را اعمال میکند. اگر مقصد نهایی Trojan نام domain باشد resolve انجام میشود؛ چه مقصد از JSON آمده باشد و چه از
destination context. آدرسهای IP بدون تغییر میمانند.
فرمت request
درخواست اولیه Trojan:
password: 56 ASCII hex bytes, hex(SHA224(password))
separator: CRLF
command: 01 CONNECT or 03 UDP ASSOCIATE
destination: ATYP + address + port
separator: CRLF
فرمت destination:
ATYP 01: IPv4 address, 4 bytes, then 2-byte big-endian port
ATYP 03: domain length, domain bytes, then 2-byte big-endian port
ATYP 04: IPv6 address, 16 bytes, then 2-byte big-endian port
در حالت UDP، درخواست اولیه UDP ASSOCIATE از 0.0.0.0:0 بهعنوان associate address استفاده میکند. مقصد واقعی راه دور
داخل هر Trojan UDP packet بعدی حمل میشود.
فرمت Trojan UDP packet:
destination: ATYP + address + port
length: 2 bytes big-endian
separator: CRLF
payload: length bytes
اولین داده و برقراری transport
Init مقصد و node بعدی را آماده میکند و بهتنهایی درخواستی ارسال نمیکند.
اولین payload معتبر میتواند پیش از Est، داخل callback آن یا هنگام Pause برسد.
درخواست کامل و تمام اولین payload در یک buffer معمولی ارسال میشوند؛ در حالت UDP
هدر یک datagram نیز در همان buffer قرار میگیرد. ورودی عادی با headroom کافی در همان buffer
استفاده میشود؛ در غیر این صورت buffer عادی best-fit ساخته میشود و prefix مقیم و بدنه pipe خصوصی به ترتیب کپی میشوند. TCP خالی درخواست را فعال نمیکند.
datagram خالی Trojan معتبر است.
در 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، بسته TCP یا رکورد TLS نیست.
مسیر مستقیم TCP و بستهبندی هر UDP در upstream، صف خروجی برای Est یا Resume ندارند. Trojan پاسخ موفقیت ندارد و TCP برگشتی از Init بدون بررسی بدنه عبور میکند. بهمحض آماده شدن درخواست کامل اولیه TCP، client وضعیت راهاندازی را نابود و timer را لغو میکند و پیش از تحویل buffer، برای آن line بهطور دائمی bypass میشود. callbackهای بعدی مستقیماً بین همسایهها روی همان line عبور میکنند. ادامه callbackهای Est تو در تو پیش از دسترسی به وضعیت client بررسی میکند که هنوز فعال باشد. lineهای application و carrier در UDP برای قاببندی، نگاشت سیگنال و پاکسازی carrier متعلق به client فعال میمانند.
line برنامه در UDP قرضی میماند و client یک carrier معمولی TCP وابسته روی همان
worker ایجاد میکند. Est و Pause/Resume و Finish به association درست نگاشت میشوند.
درخواست از 0.0.0.0:0 استفاده میکند و هر فریم مقصد، طول و CRLF دارد.
decoder تمام datagramهای کامل ورودی پذیرفتهشده را حتی پس از Pause تحویل میدهد.
ورودی callback تو در تو پشت ورودی قدیمی در FIFO parser قرار میگیرد؛ تنها قطعه
ناقص یا وضعیت لازم برای ترتیب باقی میماند. Finish، ورودی خراب یا رد گیرنده،
تحویل را متوقف و association را میبندد. این node فقط carrier متعلق به خودش را
نابود میکند؛ مالک برنامه در shutdown همین carrier وابسته را نیز تخلیه میکند.
Splice و محدودیت نگهداری
اولین درخواست و payload معمولی هستند؛ خود درخواست حداکثر 320 بایت است. TCP بعدی هویت buffer معمولی یا splice را حفظ میکند. UDP از padding اعلامشده 331 بایتی استفاده میکند؛ هدر هر datagram همچنان حداکثر 263 بایت است. parser فقط هدر لازم را میخواند و حرکت دقیق بدنه، مرز datagram و padding کامل مسیر بعدی را حفظ میکند. کمبود padding یا ظرفیت pipe با fallback کامل معمولی مدیریت میشود.
| ورودی نگهداریشده parser | سقف منطقی شامل برابری |
|---|---|
| ورودی UDP با head فعال و هدر cacheشده | 2,162,950 بایت، برابر 2 MiB بهعلاوه یک فریم حداکثری 65,798 بایتی |
| بدنه UDP | حداکثر 65,535 بایت؛ بدنه خالی معتبر است |
برابری مجاز است؛ سرریز یا رد صف parser همان association را میبندد. صف خروجی decodeشده و محدودیت 1,024 رکورد برای batch همزمان وجود ندارد. تعداد قطعههای ورودی سهمیه خروجی را مصرف نمیکند. این حدود از اندازه pool، پروفایل RAM و ظرفیت pipe مستقلاند. datagram محلی بیشازحد بزرگ حذف میشود و فریم دریافتی خراب association را میبندد.
فیلد طول دوبایتی در هر دو جهت payload از صفر تا 65,535 بایت را میپذیرد؛ آدرس، طول و CRLF حداکثر 263 بایت دیگر به فریم اضافه میکنند. این سقف framing در Trojan است، نه تضمین پذیرش همهٔ اندازهها توسط socket UDP یا peer. برای نمونه، UDP روی IPv4 معمولی و بدون option حداکثر 65,507 بایت payload دارد؛ مسیر یا peer ممکن است سقف پایینتری داشته باشد. هر payload پذیرفتهشده یک datagram میماند. TCP میتواند فریم را در چند read تحویل دهد، ولی Trojan بدنه را کوتاه یا به چند datagram برنامه تقسیم نمیکند.
splice نیازمند misc.splice، build مناسب و پشتیبانی تمام nodeهای chain است.
DomainResolver پشتیبانی میکند؛ TlsClient همچنان splice را برای کل chain
غیرفعال میکند. در استقرار معمول TLS را نگه دارید؛ این قابلیت تضمین zero-copy
یا افزایش سرعت اندازهگیریشده نیست.
رفتار Finish
TrojanClient پیش از فرستادن Finish واقعی، line state خود را از بین میبرد. برای lineهای معمولی بیرونی هم
lineDestroy() را فراخوانی نمیکند.
در حالت UDP، مالک TCP carrier line داخلیای است که خودش ساخته است. بسته شدن app line یا carrier line باعث بسته شدن سمت مقابل و از بین رفتن امن carrier line میشود.
padding
TrojanClient برای درخواست association UDP با اندازه ۶۸ بایت و بزرگترین هدر datagram با اندازه ۲۶۳ بایت
که همراه اولین payload ارسال میشوند، padding رزرو میکند:
required_padding_left = 331
هدر datagram شامل ATYP + domain length + 255-byte domain + port + payload length + CRLF است.
همین بودجه بزرگترین درخواست TCP با اندازه ۳۲۰ بایت را نیز پوشش میدهد.
قابلیتهای پشتیبانینشده
این پیادهسازی موارد زیر را ایجاد یا مدیریت نمیکند:
- TLS
- WebSocket، HTTP، gRPC یا transport wrapperهای دیگر
- fallback سمت server
- extensionهای غیر استاندارد Trojan
وقتی به این layerها نیاز دارید، از nodeهای جداگانه WaterWall مثل TlsClient، HttpClient، MuxClient یا
TcpConnector استفاده کنید.
اشتباههای رایج
- انتظار نداشته باشید
TrojanClientخودش به سرور Trojan وصل شود؛ بعد از آنTlsClientو connector بگذارید. - در deployment عمومی Trojan،
TlsClientرا حذف نکنید مگر اینکه عمداً Trojan را بهصورت plain روی wire بخواهید. - آدرس سرور Trojan را با آدرس مقصد نهایی اشتباه نگیرید.
- حالت UDP را با node بعدی غیر stream-facing استفاده نکنید.
متادیتای 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 | 331 بایت |