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

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:

fieldtypeتوضیح
namestringنام دلخواه node؛ باید داخل config یکتا باشد.
typestringباید دقیقاً "TrojanClient" باشد.
settingsobjectتنظیمات Trojan client؛ نباید خالی باشد.
nextstringnode stream-facing که بایت‌های پروتکل Trojan را حمل می‌کند؛ در استفاده معمول لازم است.

فیلدهای اجباری داخل settings:

fieldنام‌های قابل قبولتوضیح
password materialpassword, pass, sha224, password-sha224, password_sha224یا password خام بدهید یا مقدار hex آماده SHA224؛ استفاده هم‌زمان از هر دو مجاز نیست.
target addresstarget-address, address, targetمقصد نهایی host/IP، یا "dest_context->address" / "line->dest_ctx->address".
target portportپورت مقصد نهایی، یا "dest_context->port" / "line->dest_ctx->port". پورت ثابت باید در بازه 1..65535 باشد.

password و pass مقدار خام password در Trojan هستند. TrojanClient مقدار SHA224(password) را محاسبه و به‌شکل hex با حروف کوچک ارسال می‌کند. مقدار alias‌های sha224 باید دقیقاً ۵۶ کاراکتر hexadecimal باشد؛ حروف بزرگ به‌طور خودکار به حروف کوچک تبدیل می‌شوند.

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

fielddefaultتوضیح
protocol"tcp"Trojan command را انتخاب می‌کند. alias آن proto است.
first-payload-timeout-ms400مهلت به میلی‌ثانیه صحیح در بازه 0 تا 4,294,967,295؛ صفر انتظار عمدی را غیرفعال می‌کند.
verbosefalseجزئیات بیشتری از 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 flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left331 بایت