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

TrojanServer

TrojanServer پیاده‌سازی سمت server پروتکل Trojan در WaterWall است. stream مربوط به Trojan را از node قبلی می‌خواند، درستی hash استاندارد SHA224 برای password را بررسی می‌کند، مقصد TCP یا UDP درخواستی را بیرون می‌کشد و ترافیک پذیرفته‌شده را به next می‌فرستد.

برای authentication می‌توانید از فهرست محلی password‌ها یا کاربران AuthenticationClient استفاده کنید. fallback اختیاری هم برای probe‌های نامعتبر و احراز هویت‌نشده در دسترس است.

این node جایگزین setup قدیمی دو node‌ای TrojanAuthServer و TrojanSocksServer شده است.

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

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

جایگاه رایج​

Trojan server عادی:

TcpListener -> TlsServer -> TrojanServer -> TcpUdpConnector

خروجی فقط TCP:

TcpListener -> TlsServer -> TrojanServer -> TcpConnector

routing بعد از authentication:

TcpListener -> TlsServer -> TrojanServer -> Router

TrojanServer خودش TLS را terminate نمی‌کند. در deployment عمومی Trojan، TlsServer را پیش از آن قرار دهید؛ در غیر این صورت hash مربوط به password و metadata مقصد روی wire دیده می‌شوند.

نمونه Minimal با local Password​

[
{
"name": "tls-in",
"type": "TlsServer",
"settings": {
"cert-file": "/etc/waterwall/fullchain.pem",
"key-file": "/etc/waterwall/privkey.pem"
},
"next": "trojan-server"
},
{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"password": "secret-password",
"connect": true,
"udp": true,
"verbose": false
},
"next": "outbound"
},
{
"name": "outbound",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}
]

چند User محلی​

{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"users": [
"secret-password",
{
"username": "alice",
"password": "another-secret"
}
],
"connect": true,
"udp": true
},
"next": "outbound"
}

در حالت allowlist محلی، TrojanServer password خام متناظر را روی line نگه می‌دارد تا Router بتواند rule‌های مربوط به password را بررسی کند. اگر object انتخاب‌شده username داشته باشد، همان username نیز برای مسیریابی روی line ثبت می‌شود.

حالت AuthenticationClient​

{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"auth-client-node-name": "auth-client",
"connect": true,
"udp": true,
"verbose": false
},
"next": "outbound"
}

در این حالت، AuthenticationClient منبع اصلی authentication است. password مربوط به Trojan را به‌صورت plaintext در فیلد password هر user قرار دهید؛ AuthenticationClient داده لازم برای lookup بر اساس SHA224 را در داخل آماده می‌کند. فیلد name کاربر، account name است و خود Trojan password نیست.

با تنظیم auth-client-node-name دیگر نمی‌توانید password محلی تعریف کنید. TrojanServer خودش یک UserController داخلی پیش از node outbound قرار می‌دهد؛ بنابراین UserController دیگری را دستی و مستقیم بعد از آن نگذارید.

نمونه fallback​

{
"name": "trojan-server",
"type": "TrojanServer",
"settings": {
"password": "secret-password",
"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 دیگری سپرد.

فیلدهای اجباری​

فیلدهای top-level:

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

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

تنظیمات local authentication​

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

fieldtypeتوضیح
passwordstringیک raw Trojan password.
passstringalias برای password. فقط یکی از password یا pass را استفاده کنید.
passwordsarray of stringsچند raw Trojan password.
usersarraypassword string‌ها یا object‌هایی با password یا pass. object‌ها می‌توانند username داشته باشند.
clientsarrayalias برای users. فقط یکی از users یا clients را استفاده کنید.

string مربوط به password نباید خالی باشد. تکرار یک password خطایی fatal در config است، حتی اگر entry‌های تکراری username‌های متفاوتی داشته باشند.

تنظیمات Database authentication​

fieldtypeتوضیح
auth-client-node-namestringنام یک node موجود AuthenticationClient داخل همان config file.

node اشاره‌شده باید در config وجود داشته باشد، از نوع AuthenticationClient باشد و به خود TrojanServer اشاره نکند. در حالت database نیز نباید password، pass، passwords، users یا clients را تنظیم کنید.

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

fielddefaultتوضیح
connecttrueTrojan TCP CONNECT با command 0x01 را فعال می‌کند.
udptrueTrojan UDP ASSOCIATE با command 0x03 را فعال می‌کند.
verbosefalseجزئیات بیشتری از روند authentication را log می‌کند.
fallback-node-nameتنظیم نشدهbranch جایگزین برای probe‌های نامعتبر و احراز هویت‌نشده. نام‌های fallback-node و fallback هم پذیرفته می‌شوند.
fallback-intentional-delay-ms7تأخیر payload‌های upstream که به fallback می‌روند؛ در delay غیرصفر jitter اعمال می‌شود. در 0، ورودی پذیرفته‌شده حتی پس از Pause هم‌زمان تکمیل می‌شود؛ Init و فراخوانی بازدرآمده ترتیب FIFO را حفظ می‌کنند. مقدار منفی مجاز نیست.
fallback-intentional-delay-jitter-ms1jitter تصادفی برای زمان‌بندی payload‌های fallback؛ با delay صفر نادیده گرفته می‌شود و نباید منفی باشد.
sweep-interval-ms1000در حالت database، این تنظیم اختیاری به UserController داخلی داده می‌شود.

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

فرمت 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
body: TCP stream bytes or Trojan UDP packets

فرمت 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

فرمت Trojan UDP packet:

destination:  ATYP + address + port
length: 2 bytes big-endian
separator: CRLF
payload: length bytes

جریان authentication​

client‌های Trojan مقدار hex(SHA224(password)) را به‌شکل ۵۶ بایت ASCII hex می‌فرستند. TrojanServer آن را decode و با روش authentication انتخاب‌شده بررسی می‌کند.

در حالت محلی، server مقدار SHA224 هر password خام را از قبل محاسبه می‌کند. در حالت database از API جست‌وجوی SHA224 در AuthenticationClient استفاده می‌شود و پس از خواندن درخواست، user handle برگشتی روی line ثبت می‌شود.

hash کامل ۵۶ بایتی password باید در نخستین callback مربوط به upstream payload حاضر باشد. در غیر این صورت line یک probe نامعتبر و احراز هویت‌نشده در نظر گرفته می‌شود: اگر fallback وجود داشته باشد بایت‌ها به آن می‌روند وگرنه line بسته می‌شود. پس از دریافت کامل hash، بخش‌های بعدی درخواست مانند CRLF، command، destination و بایت‌های ابتدایی body می‌توانند در callback‌های بعدی برسند.

حالت Trojan بدون authentication وجود ندارد.

رفتار runtime برای TCP CONNECT​

برای command 0x01، server:

  1. password hash را بررسی می‌کند
  2. destination درخواستی را می‌خواند
  3. destination را داخل line->routing_context.dest_ctx می‌نویسد
  4. credential‌های local یا database را برای routing downstream روی line record می‌کند
  5. node next را مقداردهی اولیه می‌کند
  6. بایت‌های TCP body را که همراه request header رسیده‌اند می‌فرستد
  7. downstream Est را فقط پس از برقرار شدن مسیر outbound انتخاب‌شده می‌فرستد

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

رفتار runtime برای UDP ASSOCIATE​

برای command 0x03، آدرس داخل request اولیه فقط metadata مربوط به association است. مقصد واقعی remote داخل هر Trojan UDP packet حمل می‌شود.

برای هر destination یکتای UDP packet، TrojanServer یک backend UDP line داخلی ایجاد یا reuse می‌کند. آن backend line destination packet را داخل line->routing_context.dest_ctx می‌گیرد، credential‌های authenticated user را حمل می‌کند، و payload خام UDP را به next می‌فرستد.

reply‌های backend UDP line‌ها دوباره داخل Trojan UDP packet framing wrap می‌شوند و به stream اصلی Trojan client برمی‌گردند. در نتیجه یک Trojan UDP association می‌تواند با چند endpoint مختلف UDP صحبت کند.

UDP packet header خراب، domain name با طول صفر، port missing یا CRLF نامعتبر باعث بسته شدن Trojan line مربوطه می‌شود. بدنهٔ UDP تمام بازهٔ صفر تا 65,535 بایت فیلد طول دو‌بایتی را می‌پذیرد. پاسخ backend هم‌زمان بسته‌بندی و بدون انتظار برای Est یا Resume ارسال می‌شود.

رفتار fallback​

fallback فقط قبل از authentication موفق استفاده می‌شود. بعد از موفق شدن authentication، داده Trojan protocol خراب باعث close شدن line می‌شود و به fallback replay نمی‌شود.

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

  • بایت‌های نامعتبر در prefix مربوط به password
  • password hash که در اولین upstream payload split شده باشد
  • password CRLF نامعتبر قبل از authentication
  • password hex نامعتبر قبل از authentication
  • lookup ناموفق password
  • نبود دسترسی به cache مجاز AuthenticationClient

وقتی fallback انتخاب شود:

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

fallback می‌تواند بدون Est خروجی، پاسخ محلی تولید کند؛ حتی پاسخ و سپس Finish در همان callback مربوط به Init یا payload. پاسخ پذیرفته‌شده حتی پس از Pause هم‌زمان ارسال می‌شود تا پیش از Finish به کلاینت برسد. هنگام پاک‌سازی پس از Finish سمت مبدأ، پاسخ‌های batch نهایی جذب می‌شوند و به سمت پایان‌یافته بازتاب داده نمی‌شوند.

delay باقی‌مانده پس از Finish زنده نگه داشته نمی‌شود. تأخیر پیش‌فرض fallback عمداً کوتاه است. delay و jitter را بر اساس رفتار سرویس عمومی‌ای تنظیم کنید که fallback قرار است شبیه آن باشد؛ این‌ها mitigation هستند، نه اثبات indistinguishability زمانی.

Splice، buffer و کنترل فشار​

TrojanServer در احراز هویت، CONNECT، ASSOCIATE و fallback buffer معمولی، splice با pipe خصوصی و ورودی ترکیبی را می‌پذیرد. parser اولیه فقط هدر لازم را می‌خواند: ۶۸ بایت برای IPv4، ۸۰ بایت برای IPv6 یا ۶۵ بایت به‌علاوه طول دامنه، با حداکثر ۳۲۰ بایت. اولین callback همچنان باید تمام ۵۶ بایت hash را داشته باشد. بایت‌های موجود CRLF پیش از احراز هویت بررسی می‌شوند؛ اگر hash پیش از رسیدن CRLF پذیرفته شده باشد، lookup تکرار نمی‌شود و CRLF خرابِ بعدی جریان را می‌بندد، بدون fallback. credentials و handle کاربر پیش از Init مسیر محافظت‌شده ثبت می‌شوند.

بدنه TCP پس از هدر بدون خواندن یا تبدیل عبور می‌کند. داده ورودی حین Init یا Est پشت داده قدیمی در FIFO می‌ماند. ورودی پذیرفته‌شده پس از Init بدون انتظار برای Est یا Resume تکمیل می‌شود. پس از بازگشت Init شاخه TCP عادی، تخلیه ورودی نگه‌داشته‌شده و انتقال Pause ثبت‌شده پیش از Init، TrojanServer وضعیت محلی line را نابود و صفر می‌کند و برای آن line به‌طور دائمی bypass می‌شود. callbackهای بعدی، از جمله Finish، مستقیم از مسیر next/prev عبور می‌کنند. credentials احراز هویت روی line_t باقی می‌مانند و bypass منتظر Est نمی‌ماند. در TCP و fallback همه اعلان‌های Est، حتی تکراری‌ها و اعلان‌های داخل Init، فوری به سمت client منتقل می‌شوند. مسیرهای مقداردهی‌شده TCP و fallback همه اعلان‌های Pause/Resume را نیز عبور می‌دهند. پیش از انتخاب شاخه فقط آخرین وضعیت upstream Pause برای Init شاخه منتخب نگه داشته می‌شود؛ Resume آن Pause معوق را لغو می‌کند.

در UDP و fallback، TrojanServer برای framing، پاک‌سازی backendهای متعلق به خود، انتخاب شاخه و replay تأخیردار فعال می‌ماند.

fallback همه بایت‌های اصلی را دقیقاً یک بار بازپخش می‌کند: ابتدا prefix بررسی‌شده در حافظه، سپس ادامه دست‌نخورده، پیش از داده جدید. Init فوری است و delay و jitter فعلی حفظ می‌شوند. هر batch تأخیردار یک buffer و یک payload است؛ انتقال دقیق داده روی فشار pipe به buffer معمولی کامل برمی‌گردد. batch تأخیردار در موعد خود منتظر Resume نمی‌ماند. هنگام Finish مبدأ، مسیر مقداردهی‌شده حداکثر یک batch نهایی را هم‌زمان دریافت می‌کند، حتی پس از Pause. مسیر هنوز داخل Init، replay نگه‌داشته‌شده را دور می‌ریزد. callback نهایی به سمت فرستنده Finish بازتاب داده نمی‌شود.

در UDP فقط هدر جاریِ ۱۱، ۲۳ یا 8 + domain_length بایتی خوانده می‌شود و هر بدنه دقیقاً یک datagram، حتی datagram خالی، تولید می‌کند. backend‌ها line معمولی متعلق به سرور روی worker کلاینت هستند و پیش از Init، مقصد، عضویت association و credentials آن‌ها آماده می‌شود. line کلاینت قرضی است و فقط سازنده‌اش آن را نابود می‌کند.

Pause هر backend یک سهم مستقل برای توقف کار جدید مبدأ carrier دارد؛ Resume فقط پس از پاک شدن تمام سهم‌ها ارسال می‌شود. ورودی پذیرفته‌شده تمام datagram‌های کامل را به‌ترتیب تکمیل می‌کند، حتی بیش از ۱۰۲۴ datagram کوچک یا خالی. ورودی بازدرآمده پشت بایت‌های قدیمی پردازش می‌شود و فقط پسوند ناقص باقی می‌ماند. Pause پاسخ کلاینت به تمام backend‌های initialized، از جمله backend تازه، منتقل می‌شود. بسته‌شدن خط پردازش را متوقف و مالکیت محلی باقی‌مانده را آزاد می‌کند.

پاسخ backend هم‌زمان بسته‌بندی و بدون صف خروجی یا انتظار برای Est و Resume فرستاده می‌شود. اولین Est واقعی backend یک بار به association کلاینت نگاشت می‌شود؛ Est‌های بعدی تکرارش نمی‌کنند. گیرنده مسئول ظرفیت محدود ورودی در حال انتقال و سیاست بستن جریان هنگام رسیدن به حد نهایی است. اگر backend منتخب هنگام Init، اعلان permission یا تحویل جاری بسته شود، association حامل هم بسته می‌شود تا datagram پذیرفته‌شده بی‌صدا حذف نشود. خطای مختص backend خارج از decode همچنان محلی می‌ماند.

داده نگهداری‌شدهسقف ثابت
ورودی اولیه/UDP، شامل head فعال و هدر cache‌شده۲٬۱۶۲٬۹۵۰ بایت، برابر 2 MiB به‌علاوه فریم ۶۵٬۷۹۸ بایتی
ترتیب موقت TCP هنگام Init یا reentry2 MiB و ۱۰۲۴ buffer
backlog تأخیردار fallback و ترتیب موقت replay2 MiB و ۱۰۲۴ buffer
cache هدر اولیه۳۲۰ بایت
بدنه UDP ورودی۶۵۵۳۵ بایت
بدنه پاسخ backend۶۵۵۳۵ بایت

مونتاژ اولیه درخواست ناقص و تحویل بعدی 2 MiB را می‌پذیرد. هدر مصرف‌شده پیش از بررسی بودجه بدنه حذف می‌شود. قطعه‌های ورودی و خروجی هم‌زمان مصرف‌شده سقف ۱۰۲۴ رکورد ندارند. برابری با حد پذیرفته می‌شود و عبور از بودجه نگهداری association را می‌بندد. مسیر مستقیم فقط برای اعمال حد بایت صف ندارد. حدود مستقل از RAM profile، اندازه buffer و ظرفیت pipe هستند.

هم دیتاگرام ورودی و هم پاسخ backend، مطابق فیلد طول دو‌بایتی، payload از صفر تا 65,535 بایت را می‌پذیرند؛ دیتاگرام خالی معتبر است. فریم حداکثر 263 بایت هدر بیرون بدنه دارد. این بازهٔ framing محدودیت socket UDP، مسیر شبکه یا peer را تغییر نمی‌دهد؛ مثلاً UDP روی IPv4 معمولی و بدون option حداکثر 65,507 بایت payload دارد. هر بدنه یک datagram می‌ماند، حتی اگر TCP فریم را در چند read تحویل دهد. Trojan بدنه را کوتاه یا به چند دیتاگرام UDP تقسیم نمی‌کند.

prepend پاسخ UDP در padding واقعی ۲۶۳ بایتی انجام می‌شود و prefix قبلی جزو payload می‌ماند. کمبود padding یا منابع pipe از helper‌های مشترک انتقال دقیق و fallback کامل به buffer معمولی استفاده می‌کند. shutdown مالک مبدأ، backend‌ها، صف‌ها و بدنه‌های خصوصی را پیش از آزاد شدن chain و pool‌ها تسویه می‌کند؛ task تأخیردار فقط عمر فیزیکی line را نگه می‌دارد.

قابلیت splice پس از افزودن UserController داخلی، helper‌های connector و شاخه fallback ارزیابی می‌شود. AuthenticationClient فقط وابستگی lookup است، نه مسیر payload. TlsServer و TlsClient همچنان splice را برای chain خود غیرفعال می‌کنند. TLS را در استقرار معمول Trojan نگه دارید. این قابلیت تضمین zero-copy همه buffer‌ها یا افزایش سرعت اندازه‌گیری‌شده نیست.

رفتار Finish​

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

client stream line توسط adapter قبلی ساخته شده و TrojanServer آن را destroy نمی‌کند. backend UDP line‌های داخلی توسط TrojanServer ساخته می‌شوند؛ این node مالک آن lineهاست و هنگام بسته شدن client stream یا backend UDP line، آن‌ها را امن از بین می‌برد.

padding​

TrojanServer ممکن است بزرگ‌ترین Trojan UDP reply header را prepend کند:

required_padding_left = 263

این مقدار ATYP + domain length + 255-byte domain + port + payload length + CRLF را پوشش می‌دهد.

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

این پیاده‌سازی موارد زیر را ایجاد یا مدیریت نمی‌کند:

  • TLS termination
  • WebSocket، HTTP، gRPC یا transport wrapper‌های دیگر
  • composition قدیمی TrojanAuthServer به علاوه TrojanSocksServer
  • extension‌های غیر استاندارد Trojan

وقتی به این layer‌ها نیاز دارید، از node‌های جداگانه WaterWall مثل TlsServer، HttpServer، MuxServer، TcpUdpConnector یا Router استفاده کنید.

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

  • TrojanServer را مستقیماً روی TCP listener عمومی قرار ندهید، مگر اینکه عمداً Trojan را به‌صورت plain روی wire بخواهید؛ برای حالت عادی قبل از آن TlsServer بگذارید.
  • local password‌ها را همراه auth-client-node-name تنظیم نکنید.
  • next را روی یک UserController دستی نگذارید؛ database mode خودش UserController داخلی می‌سازد.
  • برای config‌های جدید از node name‌های قدیمی TrojanAuthServer و TrojanSocksServer استفاده نکنید.
  • انتظار نداشته باشید آدرس اولیه UDP ASSOCIATE مقصد نهایی UDP را تعیین کند؛ هر Trojan UDP packet آدرس مقصد خودش را حمل می‌کند.

متادیتای node​

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

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