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

TlsClient

TlsClient لایه TLS سمت client در WaterWall است و از نسخه BoringSSL موجود در همین repository استفاده می‌کند. داده cleartext را از نود قبلی می‌گیرد و پس از رمزنگاری به‌شکل TLS record به نود بعدی می‌فرستد. TLS recordهای برگشتی نیز در جهت downstream رمزگشایی می‌شوند و به‌صورت cleartext به نود قبلی برمی‌گردند.

این نود یک handshake واقعی TLS در سمت client انجام می‌دهد. ساختار handshake تا حد ممکن به Chromeهای جدید نزدیک شده است؛ از جمله در پیش‌فرض‌های ALPN، ‏ALPS، استفاده از GREASE، جابه‌جایی ترتیب extensionها، پشتیبانی از certificate compression و گروه hybrid اختیاری X25519MLKEM768.

جایگاه رایج

chain سمت client برای HTTPS:

HttpClient -> TlsClient -> TcpConnector

chain سمت client همراه با proxy protocol:

TrojanClient -> TlsClient -> TcpConnector
VlessClient -> TlsClient -> TcpConnector

TlsClient خودش socket باز نمی‌کند. معمولاً بعد از آن نودی مثل TcpConnector قرار می‌گیرد تا byteهای رمزنگاری‌شده TLS را به server راه دور برساند.

نمونه

[
{
"name": "tls-client",
"type": "TlsClient",
"settings": {
"sni": "example.com",
"alpns": ["http/1.1"],
"verify": true,
"x25519mlkem768": true,
"verbose": false
},
"next": "server-out"
},
{
"name": "server-out",
"type": "TcpConnector",
"settings": {
"address": "example.com",
"port": 443,
"nodelay": true
}
}
]

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

فیلدهای top-level:

FieldTypeتوضیح
namestringنام دلخواه نود؛ باید داخل config یکتا باشد.
typestringباید دقیقاً "TlsClient" باشد.
settingsobjectتنظیمات TLS client.
nextstringنود stream-facing که TLS recordهای رمزنگاری‌شده را حمل می‌کند؛ در استفاده معمول لازم است.

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

FieldTypeتوضیح
snistringمقدار Server Name Indication در TLS با طول ۱ تا ۲۵۵ byte.

وقتی verify فعال باشد، همین sni برای certificate verification هم استفاده می‌شود.

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

FieldDefaultتوضیح
alpns["h2", "http/1.1"]فهرست ترتیبی protocolهای پیشنهادی ALPN؛ ترتیب JSON عیناً در ClientHello حفظ می‌شود.
verifytrueبررسی certificate متعلق به peer را در BoringSSL فعال می‌کند.
x25519mlkem768trueبرای نزدیک‌تر شدن رفتار به Chrome، گروه hybrid با نام X25519MLKEM768 را اعلام می‌کند.
tls13-record-shapingتنظیم نشدهتنظیم آزمایشی padding و delay سمت فرستنده برای TLS 1.3.
verbosefalseجزئیات بیشتری از state مربوط به TLS را log می‌کند.

با verify: true، فایل CA داخلی utils/cacert.h در context مربوط به BoringSSL بارگذاری می‌شود. اگر verify را false بگذارید، handshake همچنان واقعی است اما زنجیره certificate برای این instance از tunnel بررسی نمی‌شود.

مقادیر پیش‌فرض اختیاری فقط وقتی اعمال می‌شوند که key مربوطه وجود نداشته باشد. اگر alpns وجود داشته باشد، باید arrayای از stringها باشد. طول هر نام باید بین ۱ تا ۲۵۵ byte باشد، نام تکراری پذیرفته نمی‌شود و طول فهرست encodeشده نباید از ۶۵٬۵۳۳ byte بیشتر شود. اگر verify، x25519mlkem768 یا verbose وجود داشته باشد، مقدار آن باید boolean واقعی JSON باشد. نوع اشتباه باعث خطای startup می‌شود و به‌معنای انتخاب مقدار پیش‌فرض نیست.

بهتر است x25519mlkem768 را فعال نگه دارید. غیرفعال کردن آن ClientHello را کوچک‌تر می‌کند، اما شباهت handshake به نسخه‌های فعلی Chrome را هم کاهش می‌دهد.

شکل‌دهی آزمایشی 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 را تغییر نمی‌دهد. اگر باید هر دو جهت ارسال شکل داده شوند، TlsClient محلی و TlsServer راه دور را جداگانه تنظیم کنید.

فرم سفارشی:

"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 واقعی بعدی ارسال نمی‌شود. اگر سمت cleartext پایان یابد، timer لغو می‌شود و تا زمانی که wire قابل نوشتن باشد ciphertext صف‌شده client به‌صورت synchronous و با ترتیب FIFO آزاد می‌شود. اگر wire در حالت pause باشد، باقی صف دور ریخته می‌شود؛ state محلی و upstream Finish پیش از بازگشت callback تکمیل می‌شوند. در طول این تخلیه نهایی همگام، downstream Payload و Est دور ریخته می‌شوند و Pause و Resume فقط state محلی wire-paused را به‌روزرسانی می‌کنند؛ هیچ‌یک از این callbackها به سمت owner پایان‌یافته cleartext فرستاده نمی‌شود. پایان سمت wire خروجی صف‌شده را لغو و دور می‌ریزد و فقط به سمت cleartext منتقل می‌شود. اگر ساخت timer شکست بخورد، recordها فوراً و به‌ترتیب تخلیه می‌شوند.

شکل‌دهی همراه با delay برای مصرف‌کنندگان داخلی handshake takeover مانند RealityClient رد می‌شود، زیرا takeover خام نمی‌تواند TLS record تأخیردار و معلق را به ارث ببرد. شکل‌دهی فقط با padding، وقتی boundary مربوط به takeover خالی است، همچنان سازگار می‌ماند.

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

ترتیب ALPN و مسئولیت هماهنگی protocol برنامه

تنظیم alpns فهرست ترتیبی پیشنهادی ALPN را در TLS ClientHello مشخص می‌کند. برای مثال:

"alpns": ["http/1.1"]

ترتیب array عیناً حفظ می‌شود. اگر alpns وجود نداشته باشد، پیش‌فرض Chrome-like زیر استفاده می‌شود:

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

قرار دادن array خالی، ALPN را غیرفعال می‌کند. key مفرد alpn پشتیبانی نمی‌شود.

ترتیب تنظیم‌شده پیشنهاد client را بیان می‌کند، اما TLS server یکی از protocolهای پیشنهادی را انتخاب می‌کند. TlsClient نتیجه این انتخاب را به نود قبلی WaterWall گزارش نمی‌دهد، mode مربوط به HTTP آن نود را عوض نمی‌کند و بررسی نمی‌کند که protocol برنامه cleartext با ALPN انتخاب‌شده یکسان باشد. مسئولیت هماهنگ کردن این تنظیمات با کاربر است.

این موضوع در chainای مثل HttpClient -> TlsClient اهمیت ویژه دارد:

  • برای WebSocket Upgrade روی HTTP/1.1، ‏HttpClient را روی HTTP/1.1 قرار دهید و در TlsClient فقط "alpns": ["http/1.1"] را پیشنهاد کنید.
  • برای WebSocket روی HTTP/2، ‏HttpClient را روی HTTP/2 قرار دهید تا از مسیر CONNECT در HTTP/2 استفاده کند و در TlsClient فقط "alpns": ["h2"] را پیشنهاد کنید.
  • پیشنهاد هم‌زمان h2 و http/1.1 معتبر و رایج است و ClientHello را به پیش‌فرض Chrome نزدیک‌تر نگه می‌دارد. وقتی این پیشنهاد را همراه با یک HttpClient با version ثابت استفاده می‌کنید، کاربر باید بداند target server کدام protocol را انتخاب می‌کند و HttpClient را برای همان version تنظیم کند. اگر انتخاب target نامشخص یا متغیر است، فقط protocol هماهنگ با HttpClient را پیشنهاد کنید.
توجه

تا ژوئیه ۲۰۲۶، برای HTTPS معمولی از طریق hostname پروکسی‌شده Cloudflare که HTTP/2 در آن فعال است، وقتی h2 پیشنهاد شود Cloudflare آن را انتخاب می‌کند. بنابراین می‌توان پیشنهاد Chrome-like برابر "alpns": ["h2", "http/1.1"] را حفظ کرد و HttpClient قبلی را روی HTTP/2 قرار داد.

این موضوع برای WebSocket صدق نمی‌کند. رفتار فعلی مستندشده Cloudflare برای WebSocketهای proxyشده از extended CONNECT مربوط به WebSocket روی HTTP/2 (RFC 8441) پشتیبانی نمی‌کند. برای WebSocket پشت Cloudflare، با قرار دادن فقط "alpns": ["http/1.1"] در TlsClient و تنظیم HttpClient روی HTTP/1.1، مسیر WebSocket Upgrade در HTTP/1.1 را اجبار کنید. چون تنظیمات و پشتیبانی provider ممکن است تغییر کند، پیش از تکیه بر هرکدام از این رفتارها قابلیت‌های فعلی Cloudflare را دوباره بررسی کنید.

وقتی انتخاب target server شناخته‌شده و ثابت است، پیشنهاد چند ALPN مناسب است. این کار application protocol را به‌صورت خودکار تشخیص نمی‌دهد؛ نود قبلی باید از قبل برای protocolای تنظیم شده باشد که server انتخاب خواهد کرد. تغییر alpns همچنین بخش قابل‌تشخیصی از ClientHello را تغییر می‌دهد و ممکن است آن را از ظاهر پیش‌فرض Chrome-like دور کند.

پس از کامل شدن handshake، ‏TlsClient نسخه TLS، ‏cipher و ALPN مذاکره‌شده را در debug log ثبت می‌کند. protocol انتخاب‌شده به‌شکل alpn="..." و نبودن ALPN مذاکره‌شده به‌شکل alpn=<none> نمایش داده می‌شود. این log فقط برای عیب‌یابی است و مقدار انتخاب‌شده همچنان به tunnel مربوط به application قبلی منتقل نمی‌شود.

TLS context با این رفتار ساخته می‌شود:

رفتارمقدار فعلی
حداقل TLS versionTLS 1.2
حداکثر TLS versionTLS 1.3
Session cacheclient session cache فعال
Session timeout7200 seconds
GREASEفعال
Extension permutationفعال
Certificate compressionBrotli decompression support
Signed certificate timestampsفعال
OCSP stapling requestفعال per line

گروه‌های پشتیبانی‌شده:

X25519MLKEM768:X25519:P-256:P-384:P-521

اگر x25519mlkem768 برابر false باشد، گروه اول حذف می‌شود:

X25519:P-256:P-384:P-521

ترتیب signature algorithmها نیز ثابت و شبیه Chrome است. نسخه BoringSSL همراه پروژه هم تغییرات محلی لازم برای شکل ClientHello و ترتیب cipherها را دارد.

ALPS

TlsClient فقط وقتی protocol متناظر در alpns وجود داشته باشد، application setting شناخته‌شده و Chrome-like آن را برای ALPS اضافه می‌کند:

ProtocolALPS payload
h2Chrome payload ثابت 02 68 32
http/1.1payload خالی

مقدار h2 همان payload خام Chrome است، نه یک HTTP/2 SETTINGS frame سریال‌شده. پس از ثبت مقدار هر protocol، BoringSSL نمایش نهایی ALPS روی wire را می‌سازد. مقدارهای سفارشی ALPN بدون ALPS payload تعریف‌شده توسط TlsClient پیشنهاد می‌شوند.

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

در upstream Init، TlsClient:

  1. state مربوط به BoringSSL را برای line مقداردهی اولیه می‌کند
  2. memory BIOها را می‌سازد
  3. SSL object را در client mode قرار می‌دهد
  4. SNI تنظیم‌شده را اعمال می‌کند
  5. upstream Init را به نود بعدی می‌فرستد
  6. SSL_connect() را اجرا می‌کند تا اولین ClientHello flight تولید شود
  7. byteهای handshake تولیدشده را در جهت upstream می‌فرستد

به همین دلیل قرار دادن این نود پیش از TcpConnector طبیعی است: connector می‌تواند ClientHello را تا برقرار شدن اتصال واقعی socket در buffer نگه دارد.

مفهوم Establishment

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

در حالت عادی، downstream Est نود بعدی بلافاصله به نود قبلی می‌رسد. اگر tunnel قبلی پیش از کامل شدن TLS handshake payload بفرستد، TlsClient آن را در صف نگه می‌دارد و بعد از پایان handshake از طریق SSL_write() ارسال می‌کند.

در handshake-takeover mode، TlsClient downstream Est معمول را تا پایان TLS handshake نگه می‌دارد. در این حالت ورودی takeover به‌صورت record کامل TLS و یکی‌یکی به BoringSSL داده می‌شود. پردازش دقیقاً پس از recordی که handshake را کامل می‌کند متوقف می‌شود تا recordهای بعدی بیرون SSL object و در اختیار dispatcher صریح باقی بمانند.

جریان Payload

cleartext upstream:

  • پیش از کامل شدن handshake در صف می‌ماند
  • بعد از handshake به SSL_write() داده می‌شود
  • TLS recordهای تولیدشده از write BIO خوانده و به نود بعدی ارسال می‌شوند

TLS recordهای downstream:

  • bytes رمزنگاری‌شده داخل read BIO نوشته می‌شوند
  • SSL_connect() handshake را جلو می‌برد
  • byteهای protocol تولیدشده در جهت upstream ارسال می‌شوند
  • byteهای application که SSL_read() رمزگشایی کرده است به نود قبلی می‌روند

در صورت خطا در بررسی certificate، نتیجه و دلیل گزارش‌شده توسط BoringSSL در log ثبت می‌شود.

API

TlsClient یک API کوچک برای ساخت raw ClientHello دارد:

generateTlsHello:<sni>

ClientHello تولیدشده از تنظیمات همین tunnel instance استفاده می‌کند، از جمله alpns و x25519mlkem768. طول SNI ورودی API باید بین ۱ تا ۲۵۵ byte باشد.

API Handshake Takeover

کد داخلی WaterWall می‌تواند از TlsClient در handshake-takeover mode استفاده کند:

APIکاربرد
tlsclientTunnelEnableHandshakeTakeover()takeover mode را روی tunnel فعال می‌کند.
tlsclientTunnelIsHandshakeCompleted()بررسی می‌کند که آیا TLS handshake یک line کامل شده است.
tlsclientTunnelGetHandshakeBinding()نسخه، cipher، randomها و sequenceهای TLS 1.2 را پیش از آزاد شدن BoringSSL ثبت می‌کند.
tlsclientTunnelDeinitAfterHandshake()فقط برای takeover فوری TLS 1.2 است و TLS 1.3 را رد می‌کند.
tlsclientTunnelBeginTakeoverDrain()dispatch خارجی post-handshake در TLS 1.3 را آغاز می‌کند و bytes خام جمع‌شده را با حفظ SSL/BIO برمی‌گرداند.
tlsclientTunnelConsumePostHandshakeRecord()دقیقاً یک record کامل TLS 1.3 را مصرف می‌کند، plaintext مربوط به cover را دور می‌ریزد و protocol output تولیدشده را upstream می‌فرستد.
tlsclientTunnelCompleteTakeover()پس از احراز boundary توسط owner، state نگه‌داشته‌شده TLS را آزاد و raw pass-through را فعال می‌کند.

این API مرحله‌ای اجازه می‌دهد NewSessionTicket و KeyUpdate قانونی تا تکمیل handoff احرازشده، TLS واقعی باقی بمانند. پس از record تکمیل‌کننده handshake، هم read BIO و هم buffer داخلی TLS در BoringSSL باید خالی باشند؛ در غیر این صورت takeover شکست می‌خورد. controlهای خام owner و protocol output تولیدشده توسط BoringSSL از یک مسیر upstream و با همان ترتیب callback عبور می‌کنند.

این API برای integration داخلی WaterWall است و setting JSON ندارد. در configهای معمول، TlsClient در تمام طول عمر line به‌عنوان wrapper TLS باقی می‌ماند.

رفتار Finish

TlsClient پیش از فرستادن Finish، state مربوط به TLS را روی line از بین می‌برد.

در پایان عادی هر جهت، Finish در همان جهت ادامه پیدا می‌کند. خطاهای fatal هنگام read/write در BoringSSL باعث پاک شدن state محلی و بسته شدن هر دو جهت می‌شوند. این نود مالک lineهای عادی بیرونی نیست و برای آن‌ها lineDestroy() را فراخوانی نمی‌کند.

TlsClient برای بستن اتصال از سیاست direct transport close استفاده می‌کند:

این انتخاب در بستن عادی برای تقلید از رفتار Chrome موردنظر این tunnel است: وقتی در این وضعیت کاربر یا برنامه اتصال را می‌بندد، Chrome بدون فرستادن TLS close_notify پیش از آن، transport را مستقیماً می‌بندد. این توضیح به این معنا نیست که Chrome در همهٔ مسیرهای TLS shutdown هرگز close_notify نمی‌فرستد.

  • در upstream Finish عادی، state مربوط به TLS آزاد می‌شود و Finish فقط به نود بعدی می‌رود
  • در downstream Finish خام از سمت transport، state مربوط به TLS آزاد می‌شود و Finish فقط به نود قبلی می‌رود
  • مسیر بستن عادی SSL_shutdown() را صدا نمی‌زند و TLS close_notify تولید نمی‌کند
  • اگر peer یک close_notify معتبر بفرستد، مصرف می‌شود، پاسخی با close_notify داده نمی‌شود، و هر دو جهت فوراً بسته می‌شوند
  • خطاهای fatal مربوط به TLS، certificate، record authentication، SSL_write() یا BIO هر جهت initialize شده را فوراً می‌بندند
  • این tunnel منتظر پاسخ shutdown از peer نمی‌ماند، TLS half-close انجام نمی‌دهد، و timer مخصوص shutdown ندارد

برای این سیاست تنظیم JSON وجود ندارد. peerهایی که shutdown کامل TLS را لازم می‌دانند ممکن است این EOF مستقیم را به‌عنوان TLS shutdown ناقص گزارش کنند.

در handshake-takeover mode، tlsclientTunnelDeinitAfterHandshake() state مربوط به TLS را بدون بستن line آزاد می‌کند و line بعد از آن به‌صورت raw passthrough ادامه می‌دهد.

Padding

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

required_padding_left = 0

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

Metadata نود

PropertyValue
Node flagkNodeFlagChainHead
Previous nodeمجاز، در استفاده عادی required
Next nodeمجاز، در استفاده عادی required
Layer groupkNodeLayerAnything
required_padding_left0
Line stateSSL object مربوط به BoringSSL، memory BIOها، handshake flagها، payloadهای upstream queue شده

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

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

  • TLS termination سمت server
  • انتخاب dynamic certificate
  • TLS روی packet lineها
  • wrapping transport غیر TLS مثل WebSocket یا HTTP به‌تنهایی

برای این layerها از نودهای جداگانه WaterWall استفاده کنید.

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

  • انتظار نداشته باشید TlsClient خودش به remote server وصل شود؛ بعد از آن connector بگذارید.
  • فرض نکنید WaterWall Est یعنی TLS handshake کامل شده است.
  • از key مفرد alpn استفاده نکنید؛ فهرست ترتیبی را با arrayِ alpns تنظیم کنید.
  • هنگام پیشنهاد چند application protocol، انتخاب ثابت target server را بدانید و نود قبلی را برای همان protocol تنظیم کنید. TlsClient انتخاب ALPN انجام‌شده توسط server را به آن نود گزارش نمی‌دهد.
  • برای اتصال عمومی، verify را فقط زمانی غیرفعال کنید که عمداً نمی‌خواهید certificate را اعتبارسنجی کنید.
  • اگر هدف شما handshake نزدیک به Chrome فعلی است، x25519mlkem768 را غیرفعال نکنید.

پیشرفته: ترفند ECH SNI

رفتار پیشرفته و تخصصی

این قابلیت بخشی از پیکربندی عادی TLS یا مسئولیت معمول TlsClient نیست. فقط زمانی از آن استفاده کنید که deployment به‌طور عمدی ساخت TLS ClientHello را با دستکاری سازگار در سطح packet هماهنگ می‌کند.

تنظیم اختیاری ech-sni-trick یک string مربوط به hostname با طول ۱ تا ۲۵۵ byte می‌پذیرد:

"ech-sni-trick": "example.net"

وقتی این گزینه تنظیم شود، TlsClient با hostname داده‌شده یک ClientHello جعلی و دوم می‌سازد و byteهای آن را به‌عنوان GREASE encrypted_client_hello payload داخل ClientHello واقعی و بیرونی قرار می‌دهد. SNI بیرونی و cleartext همچنان settings.sni باقی می‌ماند.

ClientHello جعلی از همان فهرست و ترتیب تنظیم‌شده alpns استفاده می‌کند، اما برای کوچک‌تر نگه داشتن payload، x25519mlkem768 را غیرفعال می‌کند. این رفتار برای هماهنگی با مکانیزم‌های packet-side مانند ترفند packet-splitting در IpManipulator طراحی شده است تا byteهایی که BoringSSL از ClientHello hash می‌کند با byteهای قرارگرفته روی wire یکسان بمانند.

مقدار این گزینه باید یک string در JSON با طول ۱ تا ۲۵۵ byte باشد؛ مقدار خارج از این محدوده یا هر نوع دیگر JSON باعث خطای startup می‌شود. API تونل generateTlsHello:<sni> نیز در صورت تنظیم بودن، این گزینه را اعمال می‌کند.