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

TlsServer

TlsServer لایه TLS سمت server در WaterWall است و از OpenSSL استفاده می‌کند. TLS recordهای رمزنگاری‌شده را از نود قبلی می‌گیرد، یک handshake واقعی در سمت server انجام می‌دهد و application data رمزگشایی‌شده را به نود بعدی می‌فرستد. داده cleartext برگشتی نیز دوباره رمزنگاری می‌شود و به‌شکل TLS record به نود قبلی برمی‌گردد.

رفتار این نود شبیه یک TLS terminator عمومی مانند nginx stream است، نه nginx http. خود نود HTTP، HTTP/2، Trojan، VLESS یا هر application protocol دیگری را که بعد از TLS قرار دارد تجزیه نمی‌کند.

جایگاه رایج

chain سمت server برای HTTP:

TcpListener -> TlsServer -> HttpServer

chain همراه با proxy protocol:

TcpListener -> TlsServer -> TrojanServer -> TcpUdpConnector
TcpListener -> TlsServer -> VlessServer -> TcpUdpConnector

TLS termination عمومی قبل از routing:

TcpListener -> TlsServer -> Router

نود بعدی application data را به‌شکل cleartext دریافت می‌کند، نه TLS record.

نمونه ساده

[
{
"name": "tls-server",
"type": "TlsServer",
"settings": {
"cert-file": "/etc/waterwall/fullchain.pem",
"key-file": "/etc/waterwall/privkey.pem",
"min-version": "TLSv1.2",
"max-version": "TLSv1.3",
"select-alpns": ["http/1.1"],
"session-cache": "none",
"session-tickets": true,
"verbose": false
},
"next": "http-server"
},
{
"name": "http-server",
"type": "HttpServer",
"settings": {},
"next": "service"
}
]

نمونه Fallback

{
"name": "tls-server",
"type": "TlsServer",
"settings": {
"cert-file": "/etc/waterwall/fullchain.pem",
"key-file": "/etc/waterwall/privkey.pem",
"fallback-node-name": "nginx-fallback",
"fallback-intentional-delay-ms": 7,
"fallback-intentional-delay-jitter-ms": 1,
"select-alpns": ["http/1.1"]
},
"next": "protected-protocol"
}

Fallback فقط تا پیش از قطعی شدن مسیر TLS قابل انتخاب است. کاربردش رسیدگی به probeهای plaintext یا malformed روی یک پورت عمومی TLS است، نه مسیریابی application پس از authentication.

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

فیلدهای top-level:

FieldTypeتوضیح
namestringنام دلخواه نود؛ باید داخل config یکتا باشد.
typestringباید دقیقاً "TlsServer" باشد.
settingsobjectتنظیمات TLS server؛ نباید خالی باشد.
nextstringنودی که پس از موفقیت TLS داده cleartext را دریافت می‌کند؛ در استفاده معمول لازم است.

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

FieldTypeتوضیح
cert-filestringمسیر فایل PEM certificate سرور.
key-filestringمسیر فایل PEM private key سرور.

هر دو فایل باید برای OpenSSL context معتبر باشند.

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

FieldDefaultتوضیح
sniتنظیم نشدهفقط clientهایی را می‌پذیرد که SNI آن‌ها دقیقاً برابر این مقدار باشد.
select-alpns["http/1.1"]فهرست انتخاب ALPN به‌ترتیب ترجیح server.
alpnsتنظیم نشدهنام قدیمی تنظیم ALPN؛ بهتر است از select-alpns استفاده کنید.
min-version"TLSv1.2"حداقل TLS version.
max-version"TLSv1.3"حداکثر TLS version.
ciphers"HIGH:!aNULL:!MD5"OpenSSL cipher string برای TLS 1.2 و قدیمی‌تر.
prefer-server-ciphersfalseserver cipher preference را فعال می‌کند.
session-timeout300عمر TLS session به ثانیه.
handshake-timeout-ms60000مهلت قطعی برای کامل شدن TLS handshake؛ مقدار 0 آن را غیرفعال می‌کند.
session-cache"none"حالت session cache.
session-cache-size20480اندازه cache داخلی OpenSSL.
session-ticketstrueTLS session ticketها را فعال می‌کند.
fallback-node-nameتنظیم نشدهbranch جایگزین برای ورودی‌ای که پیش از قطعی شدن مسیر TLS، آشکارا غیر TLS است. نام‌های fallback-node و fallback هم پذیرفته می‌شوند.
fallback-intentional-delay-ms7تأخیر عمدی برای payloadهای upstream در fallback؛ مقدار 0 آن را غیرفعال می‌کند.
fallback-intentional-delay-jitter-ms1jitter تصادفی برای زمان‌بندی payloadهای fallback؛ اگر delay صفر باشد نادیده گرفته می‌شود.
tls13-record-shapingتنظیم نشدهتنظیم آزمایشی padding و delay سمت فرستنده برای TLS 1.3.
verbosefalseجزئیات بیشتری از lifecycle مربوط به TLS را log می‌کند.

نمی‌توانید sni و fallback را هم‌زمان تنظیم کنید؛ وگرنه ممکن بود یک ClientHello معتبر با SNI متفاوت به‌عنوان plaintext به fallback برسد و رفتار سرور را قابل fingerprint کند.

شکل‌دهی آزمایشی TLS 1.3 Record

تنظیم tls13-record-shaping به‌صورت اختیاری نخستین application recordهای TLS 1.3 را که همین نود می‌فرستد pad می‌کند و به تأخیر می‌اندازد. اگر این تنظیم وجود نداشته باشد، قابلیت غیرفعال است. این قابلیت هرگز ciphertext دریافتی، handshake recordها، alertها، application recordهای خالی، byteهای fallback یا recordهای TLS 1.2 را تغییر نمی‌دهد. اگر باید هر دو جهت ارسال شکل داده شوند، TlsServer محلی و TlsClient راه دور را جداگانه تنظیم کنید.

فرم سفارشی:

"tls13-record-shaping": {
"scope": {"first-application-records": 8},
"outcomes": [
{
"probability": 50,
"padding-bytes": [100, 200],
"delay": {"probability": 75, "ms": [10, 20]}
},
{"probability": 10, "padding-bytes": [400, 600]}
]
}

padding-bytes و delay.ms یک integer یا بازه integer شامل دو سر [minimum, maximum] می‌پذیرند. تعداد outcomeها باید بین ۱ تا ۱۶ باشد؛ first-application-records بین ۱ تا ۱۰۲۴، padding بین ۱ تا ۴۰۹۶ byte و delay بین ۰ تا ۱۰۰۰ millisecond است. probabilityهای outcome به‌صورت تجمعی و mutually exclusive تفسیر می‌شوند و مجموع آن‌ها نباید از ۱۰۰ بیشتر باشد. هر بار حداکثر یک outcome انتخاب می‌شود و درصد استفاده‌نشده record را بدون تغییر می‌گذارد. probability مربوط به delay فقط پس از انتخاب همان outcome بررسی می‌شود. key ناشناخته و مقدار غیرinteger یا خارج از محدوده باعث خطای startup می‌شود. هر record واجد شرایط یک جایگاه از scope مصرف می‌کند، حتی اگر هیچ outcomeای انتخاب نشود.

در حال حاضر فقط فرم سفارشی شامل scope و outcomes پذیرفته می‌شود. تا زمانی که اندازه‌گیری‌های representative برای capture، overhead، موفقیت connection و classifier انتشار یک preset versionشده را توجیه نکنند، key به نام profile رد می‌شود.

Padding از byteهای صفر استاندارد TLS 1.3 در TLSInnerPlaintext استفاده می‌کند و به‌اندازه انتخاب‌شده به ciphertext می‌افزاید، البته تا ظرفیت قانونی باقی‌مانده در TLS record. Delay پس از رمزنگاری اعمال می‌شود و latency انتخاب‌شده را اضافه می‌کند. ترتیب recordها روی wire با رابطه release_at = max(now + delay, previous_release_at) حفظ می‌شود. ciphertext صف‌شده برای هر line حداکثر ۱ MiB است؛ در ۷۶۸ KiB به producer backpressure داده می‌شود و در ۳۸۴ KiB دوباره آزاد می‌شود. انتقال Pause و Resume از state فعلی wire پیروی می‌کند: اگر تخلیه هنگام Resume به‌صورت re-entrant یک Pause دیگر از wire دریافت کند، Resume قدیمی تا رسیدن Resume واقعی بعدی ارسال نمی‌شود. پس از آنکه close_notify همتا سمت cleartext را پایان داد، TlsServer هیچ Payload، Pause یا Resumeای به آن سمت نمی‌فرستد؛ application byteهایی که بعداً دریافت شوند یا پیش‌تر توسط یک callback بیرونی رمزگشایی شده باشند دور ریخته می‌شوند. اگر سمت cleartext پایان یابد، ciphertext پذیرفته‌شده server و یک close_notify بدون padding پیش از downstream Finish تخلیه می‌شوند. پایان سمت wire خروجی صف‌شده را لغو و دور می‌ریزد و فقط به سمت cleartext منتقل می‌شود. اگر ساخت timer شکست بخورد، recordها فوراً و به‌ترتیب تخلیه می‌شوند.

Record shaping می‌تواند اندازه و زمان‌بندی recordهای ابتدایی را کمتر قابل پیش‌بینی کند، اما نمی‌تواند یک round trip داخلی handshake را حذف کند، burst را کوچک‌تر کند یا همه ویژگی‌های جهت و زمان‌بندی را پنهان کند. وقتی multiplexing برای deployment مناسب است، آن را همراه MuxClient/MuxServer استفاده کنید.

TLS Versionها

مقادیر قابل قبول:

TLSv1
TLSv1.1
TLSv1.2
TLSv1.3
TLS1.0
TLS1.1
TLS1.2
TLS1.3
1.0
1.1
1.2
1.3

min-version نباید از max-version بزرگ‌تر باشد.

ALPN Selection

TlsServer از ALPN فقط برای انتخاب protocol استفاده می‌کند، نه برای کنترل دسترسی.

ترتیب اعضای array همان ترتیب ترجیح server است:

"select-alpns": ["h2", "http/1.1"]

اگر client هر دو مقدار را ارائه کند، h2 انتخاب می‌شود. اگر ALPN ارسال‌شده با هیچ گزینه‌ای مشترک نباشد، handshake بدون ALPN توافق‌شده ادامه پیدا می‌کند. وقتی client اصلاً ALPN نفرستد، callback انتخاب در OpenSSL اجرا نمی‌شود و handshake باز هم بدون ALPN ادامه می‌یابد.

modeهای select-alpns:

Configurationرفتار
نوشته نشدهمقدار پیش‌فرض ["http/1.1"] به کار می‌رود.
[]انتخاب ALPN کاملاً غیرفعال می‌شود.
array غیرخالینخستین مقدار تنظیم‌شده‌ای انتخاب می‌شود که client نیز آن را ارائه کرده باشد.

alpns قدیمی است، اما همچنان خوانده می‌شود. هر عضو می‌تواند یک string یا objectی با فیلد value باشد. alpns و select-alpns را هم‌زمان با مقدار غیرخالی تنظیم نکنید.

ALPN، protocol انتخاب‌شده را به tunnelهای بعدی اعلام نمی‌کند. اگر h2 را ارائه می‌کنید، chain بعد از TlsServer باید واقعاً بتواند رفتاری قابل‌قبول برای HTTP/2 نشان دهد؛ در غیر این صورت deployment راحت‌تر fingerprint می‌شود.

Session Cache

مقادیر پشتیبانی‌شده برای session-cache:

Valueرفتار
"none"حالت server cache در OpenSSL، با lookup و store داخلی غیرفعال؛ مقدار پیش‌فرض.
"off"session cache را غیرفعال می‌کند.
"builtin"cache داخلی server در OpenSSL را با session-cache-size فعال می‌کند.
"builtin:SIZE"cache داخلی را فعال می‌کند و اندازه را همان‌جا می‌گیرد؛ برای مثال "builtin:4096".

حالت shared در پیاده‌سازی فعلی پشتیبانی نمی‌شود.

رفتار در زمان اجرا

در upstream Init، TlsServer:

  1. state مربوط به OpenSSL را برای line می‌سازد
  2. memory BIOها را می‌سازد
  3. SSL object را در server mode قرار می‌دهد
  4. مگر با handshake-timeout-ms: 0، deadline مربوط به handshake را فعال می‌کند
  5. اگر fallback تنظیم نشده باشد، branch محافظت‌شده next را بلافاصله راه می‌اندازد
  6. در صورت وجود fallback، منتظر byteهای اولیه می‌ماند تا مسیر TLS یا fallback را انتخاب کند

byteهای رمزنگاری‌شده upstream از نود قبلی وارد OpenSSL می‌شوند. پس از پایان TLS handshake، application data رمزگشایی‌شده به next می‌رود.

داده cleartext برگشتی از next تا کامل شدن TLS handshake در صف می‌ماند. سپس با SSL_write() رمزنگاری می‌شود و به‌شکل TLS record در جهت downstream فرستاده می‌شود.

مفهوم Establishment

در WaterWall، Est یعنی transport زیرین برقرار شده است؛ نه اینکه TLS handshake هم پایان یافته باشد.

TlsServer downstream Est را فقط یک بار می‌فرستد و ممکن است این اتفاق پیش از پایان TLS handshake رخ دهد. آماده بودن TLS در state داخلی و پس از پایان handshake در OpenSSL مشخص می‌شود.

رفتار Fallback

Fallback فقط تا پیش از قطعی شدن مسیر TLS قابل انتخاب است.

classifier، byteهای ابتدایی upstream را بررسی می‌کند:

  • byteهایی که آشکارا TLS نیستند به fallback می‌روند
  • byteهایی که شبیه TLS handshake شروع می‌شوند، مانند 16 03، در مسیر TLS می‌مانند
  • با تولید ServerHello توسط OpenSSL، مسیر line روی branch محافظت‌شده TLS قطعی می‌شود

با شروع fallback، TlsServer منابع TLS مربوط به line را آزاد و branch جایگزین را مقداردهی اولیه می‌کند. byteهای ذخیره‌شده پس از delay و jitter تنظیم‌شده، بدون تغییر به آن branch فرستاده می‌شوند. از آن به بعد payloadها بدون رمزنگاری یا رمزگشایی TLS از fallback عبور می‌کنند.

handshakeهایی که ظاهر TLS دارند اما malformed، بیش‌ازحد بزرگ، ناقص یا کند هستند در همان مسیر TLS بسته می‌شوند؛ پس از تشخیص اولیه دیگر به fallback فرستاده نخواهند شد.

تأخیر fallback فقط روی payloadهای upstream اعمال می‌شود و پاسخ‌های downstream عمداً به تأخیر نمی‌افتند. اگر upstream Finish در حالی برسد که payloadی هنوز در صف تأخیر است، Finish تا تحویل آن payload صبر می‌کند.

برای camouflage روی یک پورت عمومی، بهتر است fallback یک سرویس واقعی TLS، معمولاً nginx، با certificate، SNI، ALPN، cipher، ticket و نسخه protocol مشابه TlsServer باشد. قرار دادن HTTP ساده پشت یک پورت عمومی TLS معمولاً خیلی راحت‌تر fingerprint می‌شود.

Handshake Timeout

handshake-timeout-ms مهلتی قطعی است که با مقداردهی line آغاز می‌شود و دریافت byteهای جدید آن را تمدید نمی‌کند. این زمان از timeoutهای listener، مانند initial-idle-timeout-ms و active-idle-timeout-ms در TcpListener، مستقل است.

فقط زمانی handshake-timeout-ms را 0 بگذارید که عمداً هیچ deadline قطعی برای TLS handshake نمی‌خواهید.

رفتار Finish

برای بستن تمیز جهت downstream، اگر TLS handshake کامل شده باشد TlsServer ابتدا alert مربوط به TLS close_notify را می‌فرستد و flush می‌کند. سپس state محلی TLS را از بین می‌برد و WaterWall Finish را در جهت downstream ادامه می‌دهد.

اگر peer یک TLS shutdown تمیز بفرستد، TlsServer آن را Finish در جهت upstream و به سمت نود بعدی در نظر می‌گیرد.

خطاهای fatal در OpenSSL باعث پاک شدن state محلی و بسته شدن جهت‌های لازم می‌شوند. در fallback mode، Finish از branch جایگزین عبور می‌کند و Finish جهت upstream، در صورت نیاز، منتظر payloadهای تأخیردار می‌ماند.

Padding

TlsServer مستقیماً چیزی به ابتدای payloadهای WaterWall اضافه نمی‌کند، بنابراین مقدار زیر را اعلام می‌کند:

required_padding_left = 0

ساخت TLS recordها از طریق memory BIOهای OpenSSL انجام می‌شود.

Metadata نود

PropertyValue
Node flagkNodeFlagChainHead
Previous nodeمجاز، در استفاده عادی required
Next nodeمجاز، در استفاده عادی required
Layer groupkNodeLayerAnything
required_padding_left0
Line stateOpenSSL SSL object، memory BIOها، handshake flagها، fallback state، pending downstream queue

نکته‌های Nginx Matching

مقادیر پیش‌فرض برای شباهت به رفتار عادی TLS server در nginx stream انتخاب شده‌اند. تغییر گزینه‌های زیر ممکن است همچنان TLS معتبری بسازد، اما خروجی دیگر شبیه آن baseline نخواهد بود:

  • ciphers
  • min-version یا max-version
  • prefer-server-ciphers
  • session-cache
  • session-tickets
  • ALPN selection
  • sni
  • fallback target و fallback timing

اگر هدفتان شباهت در سطح wire به nginx پیش‌فرض است، این مقدارها را تغییر ندهید مگر اینکه اندازه‌گیری مشخصی دلیل آن را نشان داده باشد.

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

  • انتظار نداشته باشید TlsServer خودش HTTP را تجزیه کند؛ برای این کار HttpServer را بعد از آن قرار دهید.
  • sni را با fallback ترکیب نکنید.
  • از ALPN برای authentication یا رد کردن اتصال استفاده نکنید؛ نبودن ALPN مشترک، handshake را بدون ALPN توافق‌شده ادامه می‌دهد.
  • h2 را اعلام نکنید مگر اینکه هر دو مسیر محافظت‌شده و fallback بتوانند پاسخی قابل‌قبول برای HTTP/2 بدهند.
  • از shared برای session cache استفاده نکنید؛ پیاده‌سازی نشده است.