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:
| field | type | توضیح |
|---|---|---|
name | string | نام دلخواه node؛ باید داخل config یکتا باشد. |
type | string | باید دقیقاً "TrojanServer" باشد. |
settings | object | تنظیمات Trojan server؛ نباید خالی باشد. |
next | string | لازم است؛ ترافیک پذیرفتهشده Trojan به این node فرستاده میشود. |
باید دقیقاً یکی از روشهای authentication را انتخاب کنید.
تنظیمات local authentication
برای local authentication، auth-client-node-name را حذف کنید و حداقل یک raw password تنظیم کنید.
| field | type | توضیح |
|---|---|---|
password | string | یک raw Trojan password. |
pass | string | alias برای password. فقط یکی از password یا pass را استفاده کنید. |
passwords | array of strings | چند raw Trojan password. |
users | array | password stringها یا objectهایی با password یا pass. objectها میتوانند username داشته باشند. |
clients | array | alias برای users. فقط یکی از users یا clients را استفاده کنید. |
string مربوط به password نباید خالی باشد. تکرار یک password خطایی fatal در config است، حتی اگر entryهای تکراری usernameهای متفاوتی داشته باشند.
تنظیمات Database authentication
| field | type | توضیح |
|---|---|---|
auth-client-node-name | string | نام یک node موجود AuthenticationClient داخل همان config file. |
node اشارهشده باید در config وجود داشته باشد، از نوع AuthenticationClient باشد و به خود TrojanServer اشاره نکند. در
حالت database نیز نباید password، pass، passwords، users یا clients را تنظیم کنید.
تنظیمات اختیاری
| field | default | توضیح |
|---|---|---|
connect | true | Trojan TCP CONNECT با command 0x01 را فعال میکند. |
udp | true | Trojan UDP ASSOCIATE با command 0x03 را فعال میکند. |
verbose | false | جزئیات بیشتری از روند authentication را log میکند. |
fallback-node-name | تنظیم نشده | branch جایگزین برای probeهای نامعتبر و احراز هویتنشده. نامهای fallback-node و fallback هم پذیرفته میشوند. |
fallback-intentional-delay-ms | 7 | تأخیر payloadهای upstream که به fallback میروند؛ در delay غیرصفر jitter اعمال میشود. در 0، ورودی پذیرفتهشده حتی پس از Pause همزمان تکمیل میشود؛ Init و فراخوانی بازدرآمده ترتیب FIFO را حفظ میکنند. مقدار منفی مجاز نیست. |
fallback-intentional-delay-jitter-ms | 1 | jitter تصادفی برای زمانبندی payloadهای fallback؛ با delay صفر نادیده گرفته میشود و نباید منفی باشد. |
sweep-interval-ms | 1000 | در حالت 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:
- password hash را بررسی میکند
- destination درخواستی را میخواند
- destination را داخل
line->routing_context.dest_ctxمینویسد - credentialهای local یا database را برای routing downstream روی line record میکند
- node
nextرا مقداردهی اولیه میکند - بایتهای TCP body را که همراه request header رسیدهاند میفرستد
- 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 پیش ازFinishfallback دریافت میکند، حتی هنگام 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 یا reentry | 2 MiB و ۱۰۲۴ buffer |
| backlog تأخیردار fallback و ترتیب موقت replay | 2 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 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 | 263 بایت |