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

IpManipulator

IpManipulator یک packet tunnel برای دست‌کاری packet‌های خام IPv4 در chain‌های لایه ۳ WaterWall است.

این node برای مسیرهایی است که payload آن‌ها یک packet خام IP است؛ مثلاً در کنار TunDevice، RawSocket، WireGuardDevice یا node‌های packet دیگر. این node را نباید در chain معمولی بایت‌های stream یا TCP قرار داد.

هشدار

بیشتر قابلیت‌های این node برای packet shaping و آزمایش رفتار middleboxها طراحی شده‌اند. نتیجه کاملاً به مسیر شبکه بستگی دارد؛ ممکن است یک trick در یک شبکه کار کند، اما در مسیری دیگر باعث حذف packet یا رفتاری نامطلوب شود.

MTU هر نمونه​

گزینهٔ اختیاری mtu از نوع عدد صحیح هنگام ساخت از misc.mtu هسته ارث می‌برد (پیش‌فرض هسته 1500). بازهٔ معتبر 68..65535 بایت است: طول کامل packet IPv4 همراه trailerهای مربوط، برای مسیرهای موجود خروجی و ترفندها. هر نمونه مقدار معتبر خود را ثابت و مستقل نگه می‌دارد؛ ساخت نمونهٔ دیگر یا تغییر بعدی هسته بر آن اثر ندارد. null، نوع نادرست، عدد اعشاری، صفر، منفی و مقدار خارج از بازه ساخت node را رد می‌کنند. پیش‌فرض ارث‌رسیدهٔ نامعتبر نیز رد می‌شود؛ یک override معتبر برای node تعیین کنید. این تنظیم اندازه‌گیری یا مذاکرهٔ MTU مسیر نیست. همچنان فعال بودن یک ترفند معتبر لازم است؛ این گزینه فیلتر عمومی ورودی نیست و fragmentation عمومی UDP اضافه نمی‌کند.

عملکرد کلی​

  • روی upstream و downstream packet payload کار می‌کند.
  • trick‌های فعال را روی packet‌های IPv4 اجرا می‌کند.
  • می‌تواند شماره پروتکل IP مربوط به TCP و UDP را تغییر دهد و در سمت مقابل بازیابی کند.
  • می‌تواند flag‌های TCP را بازنویسی کند.
  • می‌تواند source/destination port اصلی را انتهای packet حمل کند و port زنده را تغییر دهد.
  • می‌تواند از packet نهایی کپی‌های اضافه بسازد.
  • می‌تواند شکل packet‌های TLS ClientHello را تغییر دهد.
  • می‌تواند TLS ClientHello را با SNI Blender به fragment‌های IPv4 تبدیل کند و با ترتیب shuffle شده بفرستد.
  • برای protocol swap و تغییر ساده flag‌های TCP، checksum‌های headerِ IPv4 و TCP را درجا به‌روز می‌کند؛ trick‌هایی که اندازه packet را تغییر می‌دهند یا payload می‌سازند، درخواست محاسبه کامل checksum می‌دهند.

این node با API تابع packettunnelCreate() ساخته می‌شود؛ بنابراین callback‌های اتصال مانند Finish، Pause، Resume و Est در مسیر عادی runtime آن قرار ندارند.

جایگاه رایج​

نمونه chain packet:

TunDevice -> IpManipulator -> RawSocket
TunDevice -> WireGuardDevice -> IpManipulator -> UdpStatelessSocket

نمونه استقرار جفت‌شده برای protocol swap:

edge A packet chain -> IpManipulator -> packet transport
edge B packet chain -> IpManipulator -> packet output

برای protocol swap دوطرفه، معمولاً باید در سمت مقابل نیز یک IpManipulator با تنظیمات سازگار قرار دهید تا packet‌ها به پروتکل اصلی خود برگردند.

اگر trick به branch کمکی مثل real-sni-upstream-node یا real-fin-upstream-node نیاز دارد، آن branch را جدا از مسیر معمول next تعریف کنید.

نمونه تنظیم​

protocol swap ساده:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"protoswap-tcp": 253,
"protoswap-udp": 252
},
"next": "next-packet-node"
}

SNI Blender:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"sni-blender": true,
"sni-blender-packets": 4
},
"next": "next-packet-node"
}

نمونه ترکیبی:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"protoswap-tcp": 253,
"protoswap-udp": 252,
"sni-blender": true,
"sni-blender-packets": 4,
"packet-duplicate": 1,
"source-port-ghost": true,
"dest-port-ghost": true
},
"next": "next-packet-node"
}

فیلدهای لازم​

فیلدنوعتوضیح
namestringنام دلخواه و یکتای node.
typestringباید دقیقاً "IpManipulator" باشد.
settingsobjectباید حداقل یک trick فعال داشته باشد.
nextstringبرای عبور عادی packet لازم است. بعضی trick‌ها صریحاً به next در سطح اصلی نیاز دارند.

اگر هیچ trick‌ای فعال نباشد، ساخت node شکست می‌خورد:

IpManipulator: no tricks are enabled, nothing to do

خلاصه trickها​

خانوادهتنظیمات اصلیجهتکاربرد
protocol swapprotoswap-tcp, protoswap-udpدو طرفتغییر شماره پروتکل IPv4 و بازیابی آن در سمت مقابل.
SNI Blendersni-blender, sni-blender-packetsupstreamقطعه‌قطعه کردن IPv4/TCP TLS ClientHello و فرستادن قطعه‌ها با ترتیب درهم.
packet duplicatepacket-duplicateمسیر نهایی ارسالارسال چند کپی اضافه از packet نهایی و سپس نسخه اصلی.
first-snifirst-sniupstreamفرستادن کپی‌های decoy از ClientHello با host name جایگزین‌شده، پیش از نسخه اصلی.
smuggle-snismuggle-sni, real-sni-upstream-nodeupstreamفرستادن ClientHello واقعی روی helper branch، ‏ClientHello generated هم‌طول روی مسیر عادی و نگه داشتن ترافیک بعدی پشت ordering deadline.
overlap-snioverlap-sniupstream همراه cleanup مربوط به close در downstreamبازنویسی یک sequence range در دو نسخه که SYN جعلی میان آن‌ها قرار دارد.
ech-sni-trickech-sni-trickupstreamفرستادن ClientHello جعلی جاسازی‌شده توسط TlsClient در payloadِ ECH، به شکل TCP segment خارج از ترتیب.
synfin-snisynfin-sniupstreamترکیب نخستین بخش واقعی TLS، packet بستن، ‏SYN جعلی، ‏ClientHello ساخته‌شده، filler اختیاری random/شبیه TLS و بقیه بایت‌های واقعی.
smuggle-finsmuggle-fin, real-fin-upstream-nodeupstream همراه ردیابی echo در downstreamتزریق `FIN
TCP flag rewriteup-tcp-bit-*, dw-tcp-bit-*دو طرفاجبار، toggle یا نگاشت دوباره flag‌های TCP.
port ghostsource-port-ghost, dest-port-ghostاعمال در upstream، بازیابی در downstreamحمل port اصلی در انتهای packet و تغییر port فعال transport.

Protocol Swap​

در IPv4، فیلد Protocol مشخص می‌کند payload از چه نوعی است. برای مثال TCP عدد 6 و UDP عدد 17 دارد.

گزینهنوعتوضیح
protoswapintegerنام مستعار قدیمی protoswap-tcp.
protoswap-tcpintegerprotocol number جایگزین برای packet‌های TCP.
protoswap-udpintegerprotocol number جایگزین برای packet‌های UDP.

بازه معتبر شماره پروتکل جایگزین از 0 تا 255 است.

اگر شماره پروتکل packet برگشتی با یکی از مقادیر جایگزین منطبق باشد، node آن را به TCP یا UDP عادی برمی‌گرداند. برای تونل دوطرفه، سمت مقابل هم باید تنظیمات سازگار داشته باشد.

مقدار جایگزین نباید برابر شماره پروتکل واقعی TCP یعنی 6 یا UDP یعنی 17 باشد؛ حتی اگر فقط یکی از این دو خانواده تنظیم شده باشد. در غیر این صورت، ترافیک واقعی با آن شماره از ترافیک map‌شده قابل تشخیص نیست. اگر swap برای هر دو خانواده TCP و UDP فعال باشد، مقدارهای جایگزین آن‌ها نیز باید متفاوت باشند.

گزینه protoswap-tcp-2 حذف شده است و config قدیمی شامل آن رد می‌شود. برای مهاجرت، تنها از mapping برگشت‌پذیر protoswap-tcp استفاده کنید. این تغییر همچنین تضمین می‌کند همه fragment‌های یک datagram از IPv4 شماره پروتکل یکسانی دریافت کنند.

ProtoSwap پیش از mapping، سهم آدرس مبدأ و مقصد در checksumِ TCP/UDP را به صفر تبدیل می‌کند و هنگام decoding سهم آدرس‌های فعلی را برمی‌گرداند. خود آدرس‌های IP تغییر نمی‌کنند. checksumِ packet map‌شده همچنان از شمارهٔ واقعی TCP (6) یا UDP (17) در pseudo-header استفاده می‌کند، نه شمارهٔ جایگزین. بنابراین تغییر آدرس بین دو سمت، checksum معتبر را پس از decoding معتبر نگه می‌دارد؛ بدون اسکن payload، افزایش اندازهٔ packet یا state برای هر flow. هر دو سمت باید همین قرارداد checksum را اجرا کنند.

بدون درخواست قبلی محاسبهٔ مجدد، این اصلاح افزایشی باقیماندهٔ checksum نامعتبر و قابلیت تشخیص خرابی payload را حفظ می‌کند. checksum صفر در UDPِ IPv4 به معنی غیرفعال بودن است و صفر می‌ماند؛ صفر حاصل از محاسبه برای UDPِ فعال به صورت 0xffff ذخیره می‌شود. صفر در TCP یک مقدار عادی checksum است.

همهٔ fragment‌ها شمارهٔ پروتکل یکسان می‌گیرند، اما تنها fragment حاوی هر دو بایت checksumِ transport اصلاح آدرس را انجام می‌دهد؛ حتی اگر checksumِ TCP پس از fragment اول ۸ یا ۱۶ بایتی در fragment بعدی باشد. fragmentation پیش از encoding یا بین دو سمت پشتیبانی می‌شود؛ خودِ تبدیل checksum به reassembly یا state ترتیب نیاز ندارد. انتخاب worker پس از decode از جدول وابستگی fragmentها استفاده می‌کند. آدرس‌های نهایی همهٔ fragment‌های یک datagram باید سازگار باشند. طول نامعتبر IPv4/transport، فیلد checksum ناقص یا هندسهٔ محلی نامعتبر fragment بدون تغییر عبور می‌کند و درخواست checksum آن حفظ می‌شود. بایت‌های اضافی buffer بیرون از طول IPv4 وارد تبدیل نمی‌شوند؛ بررسی طول در writer نهایی همچنان برقرار است.

شکل‌دهی packet native و port ghost پیش از normalization در upstream اجرا می‌شوند. در هر دو جهت callback، درخواست قبلی checksum باید پیش از encoding با پروتکل واقعی و آدرس‌های واقعی کامل شود؛ شکست آن مانع mapping می‌شود. برای fragment‌ها این درخواست فقط headerِ IPv4 را اصلاح می‌کند. در decoding، سهم آدرس و پروتکل واقعی پیش از بازیابی port یا parse کردن TCP/TLS برگردانده می‌شوند. درخواست قبلی در downstream به مسیر بعدی تحویل داده می‌شود؛ decoding در upstream می‌تواند همان‌جا آن را کامل کند. ProtoSwap درخواست جدیدی برای محاسبهٔ مجدد ایجاد نمی‌کند. ویرایش دلخواه payload در حالت map‌شده به تبدیلی نیاز دارد که این قرارداد checksum را بشناسد.

اگر یک packet در upstream از قبل مقدار جایگزین تنظیم‌شده این node را داشته باشد، IpManipulator این node را بخش صریح unwrap در یک جفت transport زنجیره‌ای در نظر می‌گیرد: پروتکل واقعی را بازیابی می‌کند، درخواست موجود checksum را با همان پروتکل کامل می‌کند و packet را بدون mapping به جلو می‌فرستد. اگر باید packet پس از یک node بعدی همچنان wrapped بماند، همان protocol swap را روی آن node فعال نکنید.

TCP Flag Rewrite​

این همان بخشی است که در مستندات قدیمی با عنوان «بازی با بیت‌های TCP» توضیح داده شده بود. ایده اصلی تغییر نکرده است: می‌توانید مقدار flag‌های TCP را اجباری یا toggle کنید، یا مقدارشان را از flag دیگری در packet بگیرید.

فرمت کلیدها:

up-tcp-bit-<flag>
dw-tcp-bit-<flag>

flag‌های پشتیبانی‌شده:

cwr, ece, urg, ack, psh, rst, syn, fin

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

off
on
toggle
flip
switch
packet->cwr
packet->ece
packet->urg
packet->ack
packet->psh
packet->rst
packet->syn
packet->fin

flip و switch نام‌های مستعار toggle هستند.

نمونه swap کردن ACK و FIN:

{
"up-tcp-bit-ack": "packet->fin",
"up-tcp-bit-fin": "packet->ack",
"dw-tcp-bit-ack": "packet->fin",
"dw-tcp-bit-fin": "packet->ack"
}

همه بیت‌ها هم‌زمان اعمال می‌شوند، نه یکی پس از دیگری. یعنی مقدارها از flag‌های packet اصلی خوانده می‌شوند، نه از نسخه‌ای که در همین مرحله تغییر کرده است.

اگر flag‌ی در حالت ساده (بدون preserve-tcp-bitflags) تغییر کند، بایت flag‌های TCP بازنویسی می‌شود و checksumِ TCP با updateIpv4TransportChecksum16() به‌صورت incremental و درجا به‌روز می‌شود. تغییر ساده flag‌ها هر درخواست محاسبه دوباره checksum را که از قبل وجود داشته حفظ می‌کند، اما درخواست جدیدی ایجاد نمی‌کند.

حفظ TCP Bitflagها​

گزینهنوعپیش‌فرضتوضیح
preserve-tcp-bitflagsbooleanfalseپیش از بازنویسی، بایت اصلی flag‌های TCP را ذخیره می‌کند تا جهت مقابل بتواند packet را بازیابی کند.

این حالت زمانی مفید است که می‌خواهید packet در یک جهت با TCP flag‌های تغییرکرده عبور کند و سپس در جهت برگشت به شکل اصلی TCP flags برگردد.

روند جهت‌ها:

  1. در جهتی که actionهای up-tcp-bit-* یا dw-tcp-bit-* دارد، IpManipulator بایت کامل flag‌های اصلی TCP را به‌عنوان آخرین بایت packet IPv4 ذخیره می‌کند.
  2. سپس TCP flag‌های فعال packet را طبق actionهای همان جهت بازنویسی می‌کند.
  3. طول کل IPv4 را یک بایت زیاد می‌کند و packet را برای محاسبه دوباره checksum علامت می‌زند.
  4. در جهت مقابل، اگر آن جهت action بازنویسی TCP-bit نداشته باشد ولی جهت دیگر action داشته باشد، آخرین بایت packet خوانده می‌شود، TCP flags از آن بازیابی می‌شود، آن بایت حذف می‌شود، طول IPv4 یک بایت کم می‌شود و checksum‌ها باید دوباره محاسبه شوند.

این actionهای تنظیم‌شده TCP-bit هستند که مشخص می‌کنند کدام جهت بایت پشتیبان را اضافه و کدام جهت آن را بازیابی کند. صرفاً فعال‌کردن این گزینه، نقش ثابتی به upstream یا downstream نمی‌دهد:

actionهای تنظیم‌شده TCP-bitرفتار upstreamرفتار downstream
فقط upstreamflag‌های اصلی را پشتیبان می‌گیرد، یک بایت اضافه می‌کند و actionهای upstream را اعمال می‌کند.flag‌ها را از آخرین بایت بازیابی و آن بایت را حذف می‌کند.
فقط downstreamflag‌ها را از آخرین بایت بازیابی و آن بایت را حذف می‌کند.flag‌های اصلی را پشتیبان می‌گیرد، یک بایت اضافه می‌کند و actionهای downstream را اعمال می‌کند.
upstream و downstreamپشتیبان می‌گیرد، بایت را اضافه می‌کند و actionهای upstream را اعمال می‌کند.پشتیبان می‌گیرد، بایت را اضافه می‌کند و actionهای downstream را اعمال می‌کند؛ هیچ جهتی بازیابی نمی‌کند.
هیچ‌کدامپشتیبان‌گیری یا بازیابی انجام نمی‌شود.پشتیبان‌گیری یا بازیابی انجام نمی‌شود.

نمونه بازنویسی upstream و بازیابی downstream:

{
"preserve-tcp-bitflags": true,
"up-tcp-bit-ack": "packet->fin",
"up-tcp-bit-fin": "packet->ack"
}

در این شکل، packet‌های upstream پیش از جابه‌جایی ACK و FIN، flag‌های اصلی را در آخرین بایت packet حمل می‌کنند. در downstream چون هیچ dw-tcp-bit-*ای تنظیم نشده است، همان بایت برای بازیابی TCP flags استفاده و سپس حذف می‌شود.

این حالت فقط روی packet کامل و fragmentنشده IPv4 TCP با header کامل IP و TCP کار می‌کند. همچنین فرض می‌کند آخرین بایت اضافه‌شده در مسیر بدون تغییر باقی می‌ماند. اگر node یا دستگاه شبکه‌ای trailing payload را حذف، بازنویسی، padding یا تفسیر کند، بازیابی ممکن است شکست بخورد یا flag اشتباه برگردد.

هشدار MTU و اندازه packet

هر عملیات بازنویسی همراه با preserve-tcp-bitflags یک بایت به packet اضافه می‌کند. پیش از خروج نهایی، اگر این بایت، بایت‌های port ghost یا هر دو باعث شوند یک packet کامل IPv4/TCP از mtu بزرگ‌تر شود، IpManipulator packet‌های داده واجد شرایط را در سطح transport به segment‌های کوچک‌تر تقسیم می‌کند. flag‌های زنده و ذخیره‌شده هر segment مستقل تعیین می‌شوند، هر segment یک مجموعه کامل trailer می‌گیرد و sequence space با درنظرگرفتن مصرف یک شماره sequence توسط SYN پیوسته می‌ماند. بنابراین یک ورودی TCP با اندازه 1500 بایت می‌تواند پشت MTU برابر 1500 در WaterWall باقی بماند و صرفاً برای این trailerها به headroom اضافی از طرف operator نیاز ندارد. اگر packet نهایی از نظر اندازه معتبر باشد اما buffer دقیقاً پر باشد، buffer بزرگ‌تر می‌شود.

IpManipulator برای این منظور IPv4 fragmentation یا reassembly انجام نمی‌دهد. UDP بزرگ‌تر از MTU، ‏RST، ‏URG پشتیبانی‌نشده و packet‌های دیگری که segmentation امن آن‌ها ممکن نیست، با logging دارای rate limit کنار گذاشته می‌شوند. در deploymentهای UDP همچنان باید operator برای بایت‌های port ghost در MTU headroom در نظر بگیرد.

در نتیجه، فعال‌کردن این گزینه به‌تنهایی بایتی اضافه یا حذف نمی‌کند؛ حداقل در یکی از جهت‌ها باید یک action بازنویسی TCP-bit تنظیم شده باشد.

قواعد ترکیب Stateful SNI​

در یک IpManipulator فقط یکی از first-sni، ‏smuggle-sni، ‏overlap-sni، synfin-sni و ech-sni-trick می‌تواند فعال باشد. هر یک از این trick‌های stateful همراه با sni-blender یا packet-duplicate در همان instance نیز رد می‌شود، چون state machine مربوط transcript خود را مستقیماً ارسال می‌کند.

اگر هر دو عملیات لازم هستند، از دو node استفاده کنید:

... -> IpManipulator(stateful SNI only)
-> IpManipulator(sni-blender and/or packet-duplicate)
-> ...

در traversal مربوط به upstream، node stateful باید ابتدا قرار گیرد. duplication پیش از node stateful، ورودی‌های آن را duplicate می‌کند و معادل duplicate کردن خروجی نهایی آن نیست. ترکیب sni-blender و packet-duplicate وقتی هیچ trick stateful مربوط به SNI فعال نباشد همچنان پشتیبانی می‌شود.

ترکیب‌های ردشده TCP-bit​

در ترکیب‌های زیر ساخت config شکست می‌خورد:

  • هر action از نوع up-tcp-bit-* همراه با first-sni، ‏smuggle-sni، overlap-sni، ‏synfin-sni یا ech-sni-trick، چه preserve-tcp-bitflags فعال باشد چه نباشد. actionهای upstream مربوط به TCP-bit پیش از بررسی stateful SNI اجرا می‌شوند، اما packet‌های ساخته‌شده یا replayشده توسط این state machineها به‌طور یکسان دوباره وارد پردازش TCP-bit نمی‌شوند.
  • preserve-tcp-bitflags همراه با حداقل یک action از نوع up-tcp-bit-* و sni-blender. بایت پشتیبان پیش از ساخت fragment‌های IPv4 توسط SNI Blender اضافه می‌شود، اما مسیر بازیابی peer از fragment‌ها عبور می‌کند و نمی‌تواند آن بایت را حذف کند.

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

  • preserve-tcp-bitflags بدون هیچ action مربوط به TCP-bit که encoder بدون اثر است؛
  • actionهای فقط downstream از نوع dw-tcp-bit-* همراه با یک trick stateful مربوط به SNI یا sni-blender، چون SYN آغازکننده flow یا ClientHello در upstream را تغییر نمی‌دهند؛
  • actionهای ساده TCP-bit در upstream همراه با sni-blender وقتی preserve-tcp-bitflags غیرفعال است.

preserve-tcp-bitflags با هر stage آینده در upstream که یک packet TCP دارای metadata را fragment یا reshape کند پشتیبانی نمی‌شود.

SNI Blender​

SNI Blender خود SNI را بازنویسی نمی‌کند؛ فقط شکل packet مربوط به TLS ClientHello را در جهت upstream تغییر می‌دهد.

گزینهنوعاجباریتوضیح
sni-blenderbooleanبلهقطعه‌قطعه کردن TLS ClientHello و فرستادن قطعه‌ها با ترتیب درهم را فعال می‌کند.
sni-blender-packetsintegerوقتی فعال است بلهتعداد fragment‌ها. مقدار معتبر 2 تا 16.

رفتار:

  • packet‌های IPv4/TCP در upstream را که TLS ClientHello دارند تشخیص می‌دهد.
  • packet‌هایی را که از قبل fragment شده‌اند نادیده می‌گیرد.
  • headerِ TCP را در fragment نخست نگه می‌دارد.
  • payloadِ IP را به چند fragment از نوع IPv4 تقسیم می‌کند.
  • offsetها را طبق قواعد IPv4 روی مرزهای هشت‌بایتی قرار می‌دهد.
  • fragment‌ها را با ترتیب درهم می‌فرستد.

نکته‌های عملی از مستندات قدیمی هنوز مهم هستند:

  • TLS ClientHello باید در لایه packet ترافیک قابل مشاهده باشد.
  • اگر application کاربر خودش TLS دارد، WaterWall لازم نیست فقط برای این trick TLS بسازد.
  • اگر chain باید خودش TLS تولید کند، باید مسیر مناسب TlsClient / TlsServer داشته باشد تا قبل از رسیدن packet به این node ClientHello قابل تشخیص شود.
  • در سناریوهای محلی یا serverless، مسیریابی و capture سیستم‌عامل باید طوری باشد که packet‌های مورد نظر از WaterWall عبور کنند.

این trick روی همه شبکه‌ها کار نمی‌کند. بعضی مسیرها packet‌های fragment‌شده را دور می‌اندازند.

Packet Duplicate​

گزینهنوعتوضیح
packet-duplicateinteger از 1 تا 16به این تعداد کپی اضافه از packet فرستاده می‌شود و سپس نسخه اصلی یک بار ارسال می‌شود.

این مرحله در مسیر نهایی ارسال و پس از دیگر trick‌های فعال اجرا می‌شود.

Port Ghost​

port ghost در مرحله نهایی ارسال upstream اعمال و در ابتدای downstream بازیابی می‌شود.

گزینهنوعتوضیح
source-port-ghostbooleansource port اصلی به انتهای packet افزوده می‌شود و source port فعال به یک high port قطعی تغییر می‌کند.
dest-port-ghostbooleandestination port اصلی به انتهای packet افزوده می‌شود و destination port فعال تغییر می‌کند.

اگر هر دو فعال باشند، ابتدا source port و سپس destination port به انتهای packet اضافه می‌شود.

در packet‌های downstream که tail مربوط را دارند، port‌های اصلی بازیابی می‌شوند و packet به طول اولیه برمی‌گردد. packet‌های fragment‌شده نادیده گرفته می‌شوند.

packet‌های داده کامل TCP، چه عادی و چه ساخته‌شده، از یک segmenter نهایی مشترک استفاده می‌کنند. هر segment خروجی دقیقاً یک trailer مربوط به port ghost و، در صورت تنظیم، یک بایت flag ذخیره‌شده با قابلیت بازیابی مستقل دریافت می‌کند. SYN/CWR فقط روی segment نخست، ‏FIN/PSH فقط روی segment پایانی و ACK/ECE قابل‌اعمال روی همه segment‌ها قرار می‌گیرد. هر segment کامل‌شده در mtu جا می‌شود و سپس duplication نهایی packet روی همان segment اعمال می‌شود.

UDP بزرگ‌تر از MTU نمی‌تواند از TCP segmentation استفاده کند و با logging دارای rate limit کنار گذاشته می‌شود. IpManipulator، ‏IPv4 fragmentation/reassembly ایجاد نمی‌کند؛ بنابراین port ghost برای UDP همچنان به MTU headroom فراهم‌شده توسط operator نیاز دارد.

branch‌های کمکی اختصاصی real-SNI و mirrored-FIN، ‏tuple اصلی خود را حفظ می‌کنند و trailer مربوط به port ghost نمی‌گیرند.

first-sni​

first-sni یک یا چند کپی decoy از TLS ClientHello خود client را، که فقط host name آن‌ها جایگزین شده است، پیش از ClientHello واقعی همان اتصال TCP روی wire قرار می‌دهد. این trick فقط در upstream عمل می‌کند و هرگز ترافیک downstream را تغییر نمی‌دهد.

بسیاری از سامانه‌های inspection یک اتصال TLS را براساس نخستین ClientHelloای که روی یک 4-tuple می‌بینند دسته‌بندی می‌کنند و همان verdict را تا پایان اتصال نگه می‌دارند. first-sni این رفتار را هدف می‌گیرد: دستگاهی که به نخستین ClientHello متکی باشد نام decoy را ثبت می‌کند، در حالی که انتظار می‌رود server مقصد فقط نسخه واقعی را دریافت کند. این همان ایده کلاسیک desynchronization با «fake packet» است که روی فیلد SNI اعمال شده است. خود ClientHello واقعی هرگز بازنویسی، fragment، split یا reorder نمی‌شود و همچنان دست‌نخورده، دقیقاً یک بار و با sequence واقعی خود فرستاده می‌شود.

گزینهنوعپیش‌فرضتوضیح
first-snistring غیرخالی با طول 1 تا 65535 بایتاجباری برای فعال‌سازیHost name نوشته‌شده در کپی‌های decoy ClientHello. فقط نخستین entry از نوع host_name در کپی بازنویسی می‌شود.
first-sni-countinteger از 1 تا 161تعداد کپی‌های جعلی پیش از نسخه اصلی.
first-sni-replay-delayinteger برحسب ms، با مقدار >= 00فاصله زمانی میان کپی‌های decoy پس از کپی نخست. فقط وقتی count بزرگ‌تر از 1 باشد اثر دارد.
first-sni-final-delayinteger برحسب ms، با مقدار >= 00تأخیر اضافه میان آخرین کپی decoy و ClientHello اصلی.
first-sni-ttlinteger از 0 تا 255حفظ TTL اصلیTTL مربوط به IPv4 برای کپی‌های decoy. مقدار 0 یک override واقعی است؛ برای حفظ TTL اصلی این فیلد را حذف کنید.
first-sni-random-tcp-sequencebooleanfalseبرای هر کپی decoy یک TCP sequence تصادفی 32 بیتی تازه انتخاب می‌کند.

اگر (first-sni-count - 1) * first-sni-replay-delay یا جمع آن با first-sni-final-delay از بازه پشتیبانی‌شده millisecond بدون علامت 32 بیتی بیشتر شود، startup شکست می‌خورد.

آنچه روی wire فرستاده می‌شود​

با count پیش‌فرض 1 و هر دو delay برابر 0، همه مراحل داخل یک callback مربوط به packet upstream انجام می‌شوند:

  1. TCP handshake دست‌نخورده است. SYN، ‏SYN|ACK و نخستین ACK بدون تغییر عبور می‌کنند.
  2. client، ‏ClientHello خود را در یک segment با sequence برابر S، طول payload برابر L و معمولاً flag‌های PSH|ACK می‌فرستد.
  3. IpManipulator این payload را parse می‌کند. یک TLS record از نوع handshake (0x16) لازم است که پیام ClientHello دقیقاً تمام body آن record را پر کند و extension از نوع server_name با دست‌کم یک entry از نوع host_name داشته باشد. payload‌ی که فقط ابتدای چنین record‌ی را دارد وارد capture چندsegmentی توضیح‌داده‌شده در ادامه می‌شود؛ سایر حالت‌ها fail-open هستند.
  4. packet clone می‌شود و فقط نخستین string از نوع host_name جایگزین می‌گردد. شش فیلد طول با delta یکسان اصلاح می‌شوند: طول host_name، طول server-name list، طول extension مربوط به server_name، طول extensions، طول handshake مربوط به ClientHello و طول TLS record؛ علاوه بر آن total length مربوط به IPv4 نیز اصلاح می‌شود.
  5. سایر بایت‌های کپی از packet اصلی هستند: IPv4 identification، ‏flags و TTL یکسان، TCP port‌ها، sequence، acknowledgement، ‏window، ‏flags و option‌های یکسان، و همان TLS version، ‏random، ‏session id، فهرست cipherها و سایر extensionها.
  6. اگر first-sni-ttl تنظیم شده باشد، TTL کپی را بازنویسی می‌کند؛ اگر first-sni-random-tcp-sequence فعال باشد نیز TCP sequence آن را جایگزین می‌کند.
  7. ابتدا decoy با checksum‌های محاسبه‌شده مجدد IPv4 و TCP ارسال می‌شود. این trick گزینه bad-checksum ندارد؛ هر دو packet همیشه با checksum معتبر خارج می‌شوند.
  8. بلافاصله پس از آن ClientHello اصلی، بدون تغییر و با checksum‌های محاسبه‌شده مجدد forward می‌شود.

Sequence numberها و دور نگه‌داشتن decoy از server​

تا وقتی first-sni-random-tcp-sequence فعال نباشد، decoy همان sequence واقعی ClientHello یعنی S را حمل می‌کند؛ بنابراین هر دو segment یک جایگاه یکسان در TCP byte stream را ادعا می‌کنند. اگر نام تنظیم‌شده d بایت بلندتر باشد، decoy بازه S تا S + L + d را می‌پوشاند، در حالی که packet واقعی S تا S + L را پوشش می‌دهد؛ نام کوتاه‌تر بازه کوچک‌تری می‌سازد. دو segment overlap دارند و هر receiver نسخه‌ای را نگه می‌دارد که سیاست overlap آن بپذیرد.

هشدار

این overlap برای دستگاه inspection هدف trick است، اما برای server مشکل ایجاد می‌کند. اگر decoy به‌صورت داده معتبر و in-window به server برسد، به TLS server تحویل می‌شود و segment واقعی بعدی duplicate محسوب خواهد شد؛ در نتیجه server به نام decoy پاسخ می‌دهد. چون TLS stack سمت client transcript را از ClientHello خودش محاسبه کرده است، چنین اتصالی معمولاً به‌جای انتقال ترافیک با خطای certificate یا handshake شکست می‌خورد.

دست‌کم یکی از این دو سازوکار را برای دور نگه‌داشتن decoy از مقصد تنظیم کنید:

  • first-sni-ttl را آن‌قدر کم انتخاب کنید که routerی میان نقطه inspection و server، decoy را drop کند. مقدار مناسب به مسیر وابسته است و باید مثلاً با traceroute اندازه‌گیری شود؛ router حذف‌کننده معمولاً ICMP time-exceeded را به آدرس client برمی‌گرداند. مقدار خیلی کم باعث می‌شود inspector اصلاً decoy را نبیند و مقدار خیلی زیاد آن را به server می‌رساند.
  • first-sni-random-tcp-sequence، decoy را بسیار بیرون از receive window سمت server قرار می‌دهد. یک TCP stack منطبق، داده out-of-window را حذف می‌کند و حداکثر یک duplicate یا challenge ACK می‌فرستد، در حالی که inspectorی که بدون اعتبارسنجی sequence numberها فقط براساس جهت reassemble می‌کند ممکن است decoy را ClientHello همین اتصال parse کند. این گزینه روی inspectorهایی که sequence و window را دنبال می‌کنند اثری ندارد.

این دو گزینه را می‌توان هم‌زمان به‌کار برد. خالی گذاشتن هر دو فقط زمانی منطقی است که مقدار تنظیم‌شده با host name واقعی برابر باشد؛ در آن صورت کپی به یک duplicate segment ساده تبدیل می‌شود و اثر decoy از بین می‌رود.

آنچه یک دستگاه میانی reconstruct می‌کند به سیاست overlap خودش بستگی دارد: inspector مبتنی بر packet دو ClientHello می‌بیند و معمولاً براساس اولی عمل می‌کند؛ inspector دارای reassembly که نخستین بایت‌های رسیده برای هر sequence range را نگه می‌دارد نیز به decoy می‌رسد؛ دستگاهی که window یا TTL را validate می‌کند یا داده دیرتر را برای range هم‌پوشان ترجیح می‌دهد، نام واقعی را می‌بیند. هیچ standardی این نتیجه‌ها را تضمین نمی‌کند؛ رفتار کاملاً وابسته به مسیر است.

تکرارها و تأخیرها​

  • کپی شماره 1 همیشه synchronously و پیش از همه چیز خارج می‌شود.
  • اگر first-sni-replay-delay برابر 0 باشد، تمام first-sni-count کپی در همان callback و پشت سر هم، بدون pacing ساخته و ارسال می‌شوند.
  • با replay delay غیرصفر، کپی‌های 2 تا N در زمان‌های 1 x delay، ‏2 x delay تا (N-1) x delay پس از کپی نخست schedule می‌شوند. همه آن‌ها از قبل ساخته می‌شوند، پس هر کپی pending تا زمان ارسال یک buffer کامل packet را نگه می‌دارد؛ count را معقول نگه دارید.
  • ClientHello اصلی پس از (count - 1) * replay-delay + final-delay millisecond آزاد می‌شود؛ یعنی اگر first-sni-final-delay برابر 0 باشد، بی‌درنگ پس از آخرین decoy.
  • در طول این پنجره، packet‌های بعدی upstream همان 4-tuple، شامل ACKها و FIN/RST، پشت packet اصلی نگه‌داشته‌شده در FIFO مخصوص flow با سقف 16 packet و 256 KiB قرار می‌گیرند تا چیزی از ClientHello دارای delay سبقت نگیرد. packet‌های downstream هرگز delay نمی‌گیرند. delay دادن flow، ACKهای خود client را هم عقب می‌اندازد؛ مجموع delayها را در حد ده‌ها millisecond نگه دارید.
  • overflow در FIFO، رسیدن packet همان flow روی worker دیگر یا ناتوانی در arm کردن timer همگی به‌ترتیب fail-open می‌شوند: ابتدا packet‌های queued flush می‌شوند و سپس packet جاری.
  • یک SYN آغازین بدون payload روی همان 4-tuple، record مربوط به flow را reset می‌کند تا tuple استفاده‌شده مجدد به‌صورت flow تازه و بدون delay آغاز شود. هر چیزی که generation قبلی هنوز نگه داشته است، به‌جای forward شدن دور ریخته می‌شود.
  • اگر هر دو delay برابر 0 باشند، هیچ timer یا state تأخیری ساخته نمی‌شود و trick کاملاً synchronous است.
  • record هر flow با stateful-flow-limit محدود می‌شود و پس از 20 دقیقه عدم فعالیت expire می‌گردد. اگر record جدید قابل admission نباشد، schedule با warning به ارسال فوری collapse می‌کند و همچنان ترتیب مستندشده حفظ می‌شود.

ClientHello چندsegmentی​

capture فقط روی segment‌ی آغاز می‌شود که payload آن واقعاً با TLS ClientHello قابل‌شناسایی شروع شود؛ بنابراین payload‌های نامرتبط و غیر TLS بدون ورود به صف prestart حدسی فوراً fail-open می‌شوند. segment‌های بعدی که کاملاً contiguous هستند تا کامل شدن TLS record append می‌شوند. سقف‌ها عبارت‌اند از 16 packet، record با اندازه 16384 بایت و timeout برابر 1500 ms از زمان آخرین segment پذیرفته‌شده.

پس از تکمیل، trick روی یک packet assembled کار می‌کند: header‌های IPv4 و TCP از segment نخست، به‌علاوه تمام بایت‌های payload captureشده. segment‌های اصلی drop می‌شوند و forward نخواهند شد. دو اثر روی wire دیده می‌شود: مقصد ClientHello واقعی را به‌صورت یک segment بزرگ‌تر و از sequence نخستین segment دریافت می‌کند، نه با الگوی segmentation اصلی client؛ و packet coalesced، ‏TCP flags، ‏window و acknowledgement number نخستین segment را حمل می‌کند. محتوای بایت‌ها و پیوستگی sequence حفظ می‌شوند. اگر packet assembled یا decoy ساخته‌شده از آن از mtu بزرگ‌تر باشد، در egress نهایی دوباره با sequence‌های پیوسته به transport segment‌ها تقسیم می‌شود.

وجود gap در sequence، segment خارج از ترتیب یا retransmitted، عبور از محدودیت packet یا record، timeout capture، یا رسیدن packet همان flow روی worker دیگر باعث می‌شود segment‌های نگه‌داشته‌شده بدون تغییر و به‌ترتیب آزاد شوند.

fragment‌های IPv4، شامل fragment نخست با MF=1 و fragment‌های بعدی با offset غیرصفر، هرگز وارد capture مربوط به First-SNI نمی‌شوند و در مسیر عادی fail-open ادامه می‌دهند.

مواردی که first-sni کاری انجام نمی‌دهد​

در شرایط زیر trick به‌صورت fail-open ترافیک را بدون تغییر forward می‌کند:

  • packet از نوع IPv4/TCP نیست یا یک fragment از IPv4 است
  • payload یک TLS ClientHello record نیست و با آن هم آغاز نمی‌شود، ClientHello در بیش از یک TLS record پخش شده است، یا طول handshake با body آن record برابر نیست
  • ClientHello، ‏extension از نوع server_name یا entry از نوع host_name ندارد
  • ClientHello یک extension از نوع TLS 1.3 pre_shared_key با PSK binderها دارد و مقدار تنظیم‌شده واقعاً بایت‌های SNI را تغییر می‌دهد. IpManipulator secret مربوط به PSK را برای محاسبه binder معتبر ندارد، پس ساخت کپی را skip می‌کند و مسیر اصلی را دست‌نخورده می‌گذارد
  • substitution باعث overflow در یک فیلد طول 16 بیتی، طول 24 بیتی handshake یا total length مربوط به IPv4 شود، یا طول غیرمثبت بسازد. دلیل log می‌شود و packet اصلی forward می‌گردد
  • buffer‌ای برای کپی packet در دسترس نباشد

slotهای capture در سطح tunnel مشترک و برای هر worker برابر 16 هستند. وقتی همه مشغول باشند، قدیمی‌ترین capture کنار گذاشته و segment‌های نگه‌داشته‌شده آن بدون تغییر آزاد می‌شوند.

تفاوت first-sni با سایر trickهای SNI​

  • sni-blender خود ClientHello واقعی را در لایه IPv4 fragment می‌کند و host name جدیدی نمی‌سازد. first-sni یک packet کامل اضافه می‌کند و نسخه واقعی را دست‌نخورده می‌گذارد.
  • smuggle-sni، ‏ClientHello واقعی را از branch کمکی جداگانه می‌فرستد و کپی ساخته‌شده را روی مسیر عادی قرار می‌دهد. first-sni هر دو را از مسیر عادی و با ترتیب decoy سپس واقعی می‌فرستد.
  • overlap-sni یک transcript چندpacketی با SYN جعلی و دو نسخه از یک sequence range می‌سازد. synfin-sni علاوه بر آن FIN/RST مصنوعی و filler شبیه TLS دارد. first-sni هیچ control packet مصنوعی منتشر نمی‌کند و ClientHello واقعی را split نمی‌کند.
  • ech-sni-trick بایت‌هایی را reorder می‌کند که از قبل داخل ClientHello واقعی وجود دارند و decoy آن زودتر توسط TlsClient جاسازی شده است؛ در آن trick IpManipulator هیچ host nameای نمی‌نویسد.

محدودیت‌ها و عوارض جانبی​

  • به‌جز host name و فیلدهای طول آن، decoy با ClientHello واقعی byte-identical است؛ از جمله IPv4 identification، ‏TLS random و session id. دستگاهی که محتوای ClientHelloها را correlate کند می‌تواند این جفت را تشخیص دهد.
  • padding extension تغییر نمی‌کند؛ بنابراین نامی با طول متفاوت، decoy‌ای با اندازه متفاوت از packet واقعی می‌سازد.
  • هر کپی تنظیم‌شده کل payload مربوط به ClientHello را دوباره می‌فرستد و نام طولانی می‌تواند کپی را از MTU بزرگ‌تر کند و در egress باعث transport segmentation بیشتر شود.
  • این trick فقط ClientHello سمت client را shape می‌کند و روی DNS، filtering مبتنی بر address یا داده‌های server اثری ندارد.
  • همه packet‌های یک flow باید روی یک worker بمانند؛ در غیر این صورت trick با warning برای آن flow fail-open می‌شود.

مثال:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"first-sni": "www.example.com",
"first-sni-count": 2,
"first-sni-replay-delay": 5,
"first-sni-final-delay": 10,
"first-sni-ttl": 6
},
"next": "next-packet-node"
}

smuggle-sni​

smuggle-sni از یک TLS ClientHello دو view وابسته به مسیر می‌سازد. پس از capture کردن ClientHello واقعی، segment‌های اصلی TCP سمت client را از real-sni-upstream-node می‌فرستد و یک ClientHello جداگانه و generated با طول برابر و host name تنظیم‌شده را روی branch عادی next قرار می‌دهد. دو view همان TCP sequence range‌ها را اشغال می‌کنند، اما به‌جای آنکه عمداً به شکل overlap مرتب‌شده روی یک branch فرستاده شوند، از entryهای متفاوت branch عبور می‌کنند.

در topology موردنظر، دستگاه inspection روی branch عادی SNI ساخته‌شده را parse می‌کند و مقصد ClientHello واقعی را از helper branch می‌گیرد. خود trick این جداسازی را تضمین نمی‌کند. اگر branch‌ها در نقطه نامناسب همگرا شوند، packet‌ها reorder شوند یا مقصد view ساخته‌شده را بپذیرد، اتصال ممکن است شکست بخورد.

گزینهنوعپیش‌فرضتوضیح
smuggle-snistring با طول 1 تا 255 بایتوجود ندارد (غیرفعال)trick را فعال و host name مربوط به ClientHello ساخته‌شده روی branch عادی را تعیین می‌کند. limit مؤثر از TlsClient داخلی می‌آید.
smuggle-sni-delay-msinteger برحسب ms، با مقدار >= 00مدت زمان پس از capture موفق تا batch ساخته‌شده eligible شود. این گزینه پنجره جداگانه مربوط به ordering ترافیک بعدی را کنترل نمی‌کند.
real-sni-upstream-nodestringهنگام فعال بودن اجباریbranch کمکی که packet‌های ClientHello واقعی captureشده را دریافت می‌کند.

تنظیم مشترک stateful-flow-limit نیز اعمال می‌شود، مقدار 32 تا 1048576 می‌پذیرد و پیش‌فرض آن 65536 است. node هم به next عادی در سطح اصلی و هم به helper نیاز دارد. helper نام‌برده باید وجود داشته باشد، خود این IpManipulator نباشد، با next تفاوت داشته باشد و هنگام ساخت chain قابل دسترسی و binding باشد.

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"smuggle-sni": "www.example.com",
"smuggle-sni-delay-ms": 20,
"real-sni-upstream-node": "real-clienthello-path"
},
"next": "generated-clienthello-path"
}

record ساخته‌شده از fingerprint مربوط به TlsClient داخلی، ترتیب ALPN پیش‌فرض h2، ‏http/1.1 و key share غیرفعال x25519mlkem768 استفاده می‌کند. timeout حدسی prestart برابر 50 ms، timeout record fragment‌شده برابر 1500 ms، پنجره ordering برابر 50 ثانیه، limitهای capture، fingerprint ساخته‌شده و header‌های packet هیچ setting اختصاصی Smuggle-SNI ندارند.

Flow و ClientHello انتخاب‌شده​

Smuggle-SNI فقط روی packet‌های کامل و fragmentنشده IPv4 TCP عمل می‌کند و تنها پس از مشاهده یک SYN آغازین بدون payload برای flow record می‌سازد. opener پذیرفته شده SYN به‌همراه ترکیب اختیاری ECE و CWR دارد؛ وجود ACK، ‏FIN، ‏RST، PSH، ‏URG، هر flag دیگری یا payload مربوط به TCP Fast Open آن را ineligible می‌کند. اگر این node opener را ندیده باشد، packet‌های بعدی آن tuple بدون پردازش Smuggle-SNI عبور می‌کنند.

دو packet نخست upstream در flow دنبال‌شده بدون تغییر عبور می‌کنند و warmup محسوب می‌شوند. معمولاً این دو packet، ‏SYN آغازین و ACK نهایی handshake سمت client هستند؛ SYN|ACK مربوط به downstream شمرده نمی‌شود. capture از packet upstream بعدی آغاز می‌شود. این قاعده ordinal یعنی ClientHelloای که در دومین packet upstream ارسال شده است در همان transmission تغییر نمی‌کند؛ فقط یک کپی eligible بعدی، مثلاً retransmission پس از warmup، می‌تواند candidate capture باشد.

در نقطه capture، ‏TCP payload باید با TLS handshake record‌ی شروع شود که handshake آن یک ClientHello دارای نخستین entry قابل‌استفاده host_name در SNI باشد. record کامل تک‌segment‌ی فوراً پذیرفته می‌شود. record ناقص نگه داشته می‌شود تا continuationهای کاملاً contiguous جمع‌آوری شوند. segment آغازین باید دست‌کم 9 بایت نخست TLS را داشته باشد تا طول record و ClientHello معلوم باشد؛ prefix کوتاه‌تر capture را شروع نمی‌کند.

  • هر continuation باید payload غیرصفر داشته باشد و با حساب معمول 32 بیتی TCP، sequence آن برابر previous.seq + previous.payload_len باشد
  • capture کامل حداکثر می‌تواند 16 TCP packet داشته باشد
  • کل TLS record شامل header پنج‌بایتی آن حداکثر می‌تواند 16384 بایت باشد
  • handshake مربوط به ClientHello باید دقیقاً یک TLS record را پر کند؛ ClientHello پخش‌شده در چند TLS record یا همراه با پیام handshake دیگری در همان record پشتیبانی نمی‌شود
  • اگر continuation معتبری آن را جلو نبرد، capture پس از 1500 ms از آخرین fragment پذیرفته‌شده expire می‌شود

ACK-only، ‏retransmission، ‏gap، ‏overlap یا continuation خارج از ترتیب با capture فعال merge نمی‌شود. این packet تلاش را پایان می‌دهد و ابتدا packet‌های retained و سپس packet جاری را روی مسیر عادی آزاد می‌کند. fragment‌های IPv4 و packet‌های غیرقابل parse این capture را bypass می‌کنند و ممکن است از segment‌های نگه‌داشته شده سبقت بگیرند؛ capture تا رسیدن continuation معتبر یا timeout pending می‌ماند.

Smuggle-SNI برای reordering packet‌ها یک speculative prestart queue هم دارد. اگر نخستین candidate capture دارای payload غیرصفر حداقل 128 بایتی باشد، prefix قابل شناسایی ClientHello نداشته باشد و layout صریحاً پشتیبانی‌نشده TLS نباشد، می‌توان آن را تا 50 ms پس از آخرین update حدسی نگه داشت تا شاید start مربوط به ClientHello با sequence پایین‌تر بعداً برسد. حداکثر 16 packet با sequence‌های متمایز نگه داشته می‌شوند. پس از رسیدن start، فقط packet‌هایی که exact next sequence را تشکیل می‌دهند وارد capture ClientHello می‌شوند؛ packet‌های باقی‌مانده نامرتبط عادی آزاد می‌شوند. timeout، ‏sequence تکراری، فشار ظرفیت یا جایگزین شدن slot حدسی، packet‌های نگه‌داشته‌شده را بدون تغییر آزاد می‌کند و flow را passthrough می‌سازد. packet کوتاه غیر TLS یا candidate از نوع ACK-only اصلاً به‌صورت حدسی نگه داشته نمی‌شود و فوراً passthrough را انتخاب می‌کند. صف prestart به‌جز سقف 16 packet و اندازه هر packet IPv4، byte cap جداگانه ندارد.

slotهای capture و prestart از pool‌های مشترک در سطح tunnel می‌آیند؛ اندازه هرکدام برای هر worker تنظیم‌شده 16 است و با سایر trick‌های دارای TLS capture مشترک‌اند. اگر pool پر باشد، قدیمی‌ترین slot فعال بدون تغییر آزاد می‌شود تا capture تازه پذیرفته شود. همه fragment‌های یک capture باید روی یک worker مربوط به WaterWall بمانند. continuation روی worker دیگر fail-open می‌شود: هر packet retained از worker مالک خودش برگردانده می‌شود، اما ترتیب global اولیه arrival قابل تضمین نیست.

نحوه ساخت دو view مربوط به ClientHello​

فرض کنید TLS record واقعی ClientHello با احتساب header پنج‌بایتی record طول R دارد. TCP packet‌های captureشده P0 ... Pn، payload‌های contiguous با طول‌های L0 ... Ln را حمل می‌کنند و از TCP sequence برابر S شروع می‌شوند. packet نهایی captureشده ممکن است بایت‌هایی پس از پایان ClientHello record داشته باشد؛ بنابراین L0 + ... + Ln می‌تواند از R بیشتر باشد.

پس از capture، ‏TlsClient داخلی یک ClientHello کاملاً جدید با smuggle-sni تنظیم‌شده می‌سازد. این یک edit درجا روی SNI یا clone از fingerprint سمت client نیست. record ساخته‌شده randomness، ‏extensionها، ‏key share و ALPN مربوط به client داخلی را دارد. باید دقیقاً به شکل یک ClientHello record parse شود، host name تنظیم‌شده را داشته باشد و طول کامل آن G شرط G = R را برآورده کند. تغییر host تنظیم‌شده می‌تواند G و در نتیجه موفقیت این برابری را عوض کند، هرچند padding و انتخاب extensionها در نسخه generated یعنی رابطه طول لزوماً یک‌به‌یک نیست.

برای هر packet captureشده Pi، ‏IpManipulator یک packet مربوط به branch عادی به نام Fi می‌سازد؛ کل packet IPv4/TCP اصلی را کپی می‌کند و فقط بخشی از TCP payload را که با نخستین R بایت stream تقاطع دارد با بایت‌های متناظر generated جایگزین می‌کند. بنابراین پیش از پردازش نهایی egress:

  • Fi همان source/destination address و port، ‏TCP sequence و acknowledgement، طول payload، ‏TCP flags، ‏window و option‌ها، ‏IPv4 identification و TTL و مرز packet مربوط به Pi را دارد
  • concatenate کردن نخستین R بایت payload مربوط به F0 ... Fn دقیقاً ClientHello generated را می‌سازد
  • بایت‌های payload پس از ClientHello record واقعی بدون تغییر از packet اصلی کپی می‌شوند
  • چون TLS payload عوض شده، checksum‌ها دوباره محاسبه می‌شوند

سپس packet‌های اصلی P0 ... Pn با ترتیب sequence بازسازی‌شده از real-sni-upstream-node ارسال می‌شوند. ارسال helper، ‏tuple اصلی را حفظ می‌کند و port-ghost trailer این node روی آن اعمال نمی‌شود. batch generated یعنی F0 ... Fn از branch عادی next می‌رود که در آن protocol swap، ‏port ghost، checksum و handling مربوط به MTU سازگار همچنان می‌توانند اجرا شوند. shaping نهایی MTU ممکن است packet بزرگ را split کند، پس مرزهای capture فقط تا پیش از آن مرحله egress حفظ می‌شوند.

originalهای helper و batch generated عادی، ادامه pipeline مربوط به Smuggle-SNI/stateful-SNI را bypass می‌کنند تا دوباره inspect نشوند. packet duplication همان node، SNI Blender، سایر trick‌های stateful SNI و rewriteهای TCP bit در upstream با Smuggle-SNI ناسازگارند. protocol swap، ‏port ghost، actionهای فقط-downstream مربوط به TCP bit و Smuggle-FIN می‌توانند با رعایت رفتار خودشان هم‌زمان باشند؛ packet‌های helper واقعی همچنان از مسیر tuple-preserving بالا استفاده می‌کنند.

ترتیب branchها، delay و barrier پنجاه‌ثانیه‌ای​

ممکن است segment نخست captureشده از قبل برای باقی ClientHello منتظر مانده باشد. پس از موفقیت capture و generation، transcript قابل مشاهده چنین است:

t0:        send real P0 ... Pn to real-sni-upstream-node
t0 + D: send generated F0 ... Fn to normal next
t0 + W: release later queued upstream packets to normal next

D = smuggle-sni-delay-ms
W = max(50000, D + 1) milliseconds

همه packet‌های واقعی پیش از eligible شدن batch جعلی به helper submit می‌شوند. در D = 0، batch کامل جعلی synchronously پس از batch واقعی منتشر می‌شود. میان segment‌های داخل هیچ‌یک از batchها pacing وجود ندارد. با این حال، latency branch‌ها می‌تواند متفاوت باشد و این ترتیب submission، ترتیب arrival پس از عبور از شبکه را تضمین نمی‌کند.

تنظیم D فقط batch generated را delay می‌دهد و زمان release ترافیک بعدی client را تعیین نمی‌کند. barrier جداگانه ordering تا t0 + W باز می‌ماند؛ برای تمام delayهای 0 تا 49999 ms، از جمله صفر، این مقدار 50 ثانیه است و برای delayهای بزرگ‌تر یک millisecond پس از due time مربوط به fake خواهد بود. packet‌های قابل parse upstream همان flow که در این پنجره می‌رسند، شامل ACK، ‏retransmission، application data و FIN/RST، پشت batch کامل generated وارد FIFO می‌شوند. packet‌های downstream هرگز وارد این barrier نمی‌شوند. packet‌های غیرقابل parse، fragment‌های IPv4 و flow‌های نامرتبط آن را bypass می‌کنند و ممکن است از ترافیک retained سبقت بگیرند.

FIFO مربوط به ترافیک بعدی حداکثر 16 packet و 256 KiB نگه می‌دارد. اگر به هر یک از limitها برسد، packet‌ای روی worker دیگر بیاید، deadline گذشته باشد یا timer قابل schedule نباشد، barrier به‌صورت fail-open باز می‌شود: ابتدا همه packet‌های generated ارسال‌نشده، سپس FIFO retained و بعد packet جاری خارج می‌شوند. این ترتیب را در هر owner worker حفظ می‌کند، اما ممکن است transcript generated را زودتر از زمان تنظیم‌شده بفرستد. FIN یا RST upstream داخل صف پشت داده قدیمی‌تر می‌ماند و پس از release، record flow را حذف می‌کند.

packet‌های speculative prestart که وارد ClientHello contiguous نشده‌اند بیرون از این post-success barrier هستند. هنگام پایان پردازش prestart از مسیر عادی آزاد می‌شوند و در حالت‌های reordering ممکن است پیش از batch generated ظاهر شوند.

آنچه هر participant ممکن است ببیند​

  • client: فقط segment‌های واقعی ClientHello را می‌فرستد. WaterWall آن‌ها را از branch عادی suppress می‌کند، از helper می‌فرستد و ممکن است packet‌های بعدی client را تا پایان ordering window نگه دارد. delayهای capture و barrier می‌توانند TCP retransmission، ‏RTO backoff، تغییر congestion window یا stall قابل مشاهده برای application ایجاد کنند.
  • Middlebox مسیر عادی: انتظار می‌رود record generated را با sequence‌های واقعی client reconstruct و SNI تنظیم‌شده را parse کند. چون اندازه segment، flags، acknowledgement و شکل کلی header‌ها از packet‌های client کپی شده است، envelope مربوط به TCP شبیه flow captureشده است؛ هرچند fingerprint و محتوای cryptographic مربوط به TLS از TlsClient داخلی WaterWall می‌آید.
  • دستگاه helper path یا مقصد: segment‌های اصلی ClientHello را پس از پایان capture دریافت می‌کند. اگر route کمکی به مقصد برسد و packet‌های generated از آن دور نگه داشته شوند، server همان TLS random، ‏extensionها، ‏key share، ‏SNI و transcript واقعی client را می‌بیند. برابر بودن طول record‌ها باعث می‌شود sequence‌های TCP بعدی هنگام بازگشت ترافیک به مسیر عادی هم‌راستا بمانند.

اگر مقصد فقط ClientHello generated را دریافت کند، ServerHello و key schedule آن با ClientHello مستقل WaterWall متناظر است، نه state مربوط به TLS client؛ پس client معمولاً قادر به ادامه handshake نخواهد بود. اگر هر دو view را بگیرد، بایت‌هایی متعارض روی sequence range‌های یکسان دریافت کرده است؛ سیاست first-arrival/last-arrival در reassembly، ‏loss، ‏retransmission، ‏normalization و ترتیب مسیر تعیین می‌کنند کدام view باقی بماند. batch واقعی نخست submit می‌شود، اما این امر رفتار first-arrival سمت server را در دو branch مجزا ایمن نمی‌کند.

در نتیجه trick به topology وابسته است: observerی که باید fake را ببیند باید روی branch عادی باشد؛ endpointی که باید TLS را کامل کند باید view واقعی branch را دریافت کند؛ و ترافیک و response‌های بعدی باید سازگار دوباره به هم بپیوندند. NAT، state مربوط به firewall، routing نامتقارن، anti-spoofing، ‏TCP normalization و MTU متفاوت branch‌ها همگی می‌توانند این آرایش را شکست دهند.

Fail-open، بسته‌شدن و طول عمر flow​

پیش از آنکه packet واقعی به helper ارسال شود، در این شرایط تلاش به branch عادی fail-open می‌شود: opener flow دیده نشده یا record آن قابل admission نیست، candidate capture پشتیبانی نمی‌شود، record malformed یا بدون SNI است، پیوستگی sequence یا worker affinity از دست می‌رود، timeout یا limit مربوط به capture/prestart می‌رسد، memory یا timer setup شکست می‌خورد، یا record generated نامعتبر است یا طول آن با R تفاوت دارد. Smuggle-SNI، originalهای retained را بدون تغییر از egress عادی forward می‌کند. در مسیرهای same-worker با ترتیب capture آزاد می‌شوند؛ recovery میان worker‌ها نمی‌تواند ترتیب global روی wire را تضمین کند.

اگر ClientHello واقعی extension از نوع TLS 1.3 pre_shared_key با PSK binder داشته باشد و SNI تنظیم‌شده با SNI واقعی فرق کند، Smuggle-SNI نیز fail-open می‌شود. WaterWall secret مربوط به resumption را برای محاسبه binderهای ClientHello تغییریافته ندارد. source فقط وقتی SNI عوض می‌شود این guard را اعمال می‌کند؛ SNI تنظیم‌شده برابر به‌خاطر این شرط رد نمی‌شود، هرچند validationهای دیگر record generated و برابری طول همچنان لازم‌اند.

پس از ارسال originalها به helper، شکست scheduler مربوط به delay نمی‌تواند آن branch transmission را undo کند و به‌جای آن batch generated را فوراً روی branch عادی آزاد می‌کند. destruction مربوط به tunnel یا flow table، buffer‌های capture، fake و FIFO را که هنوز held هستند در teardown دور می‌اندازد و forward نمی‌کند.

یک FIN یا RST upstream هنگام prestart یا capture مربوط به ClientHello، originalهای held را روی مسیر عادی flush و flow را حذف می‌کند. در مسیر عادی same-worker، packet‌های held پیش از close submit می‌شوند؛ recovery میان worker‌ها ترتیب global را تضمین نمی‌کند. در post-success barrier، close پشت ترافیک قدیمی‌تر upstream queue می‌شود. FIN یا RST downstream هرگز delay نمی‌گیرد؛ capture ناتمام را flush می‌کند، اما flow post-success را فوراً حذف می‌کند و packet‌های generated یا follow-on upstream را که هنوز delay دارند دور می‌اندازد. سایر ترافیک downstream بدون تغییر است. hook سبک downstream می‌تواند ServerHelloای را که در یک TCP payload کامل آغاز می‌شود log کند، اما outcome مربوط به branch را validate نمی‌کند و packet را تغییر نمی‌دهد.

rewriteهای TCP bit در downstream می‌توانند در همین node فعال باشند و پیش از hook logging/close اجرا می‌شوند. بنابراین اضافه یا حذف کردن FIN/RST با یک setting از نوع dw-tcp-bit-* روی اینکه Smuggle-SNI close downstream را ببیند و cleanup را انجام دهد نیز اثر می‌گذارد.

برای flow پذیرفته‌شده، چه success و چه fail-open معمولی، four-tuple نرمال‌شده در state مربوط به passthrough باقی می‌ماند و Smuggle-SNI دوباره تلاش نمی‌کند. activity می‌تواند record را تا 20 دقیقه idle نگه دارد. SYN آغازین معتبر یک generation جدید می‌سازد و به‌جای replay کردن بایت‌های قدیمی، packet‌های قدیمی capture/prestart و delay barrier قبلی را دور می‌اندازد. FIN/RST upstream یا downstream معمولاً record را طبق رفتار بالا حذف می‌کند.

تفاوت smuggle-sni با سایر trickها​

  • first-sni، ‏SNI و فیلدهای طول ClientHello واقعی را در کپی‌های اضافه edit می‌کند و decoy و original را هر دو روی branch عادی می‌فرستد. Smuggle-SNI کل record جایگزین را generate می‌کند، برابری طول را لازم دارد و packet‌های واقعی را از helper route می‌کند.
  • overlap-sni و synfin-sni روی branch عادی sequence range‌های متعارض و TCP control packet‌های مصنوعی منتشر می‌کنند. Smuggle-SNI هیچ SYN، ‏FIN یا RST جعلی ندارد؛ split view آن از routing دو branch ایجاد می‌شود.
  • ech-sni-trick یک decoy byte-identical را که از قبل TlsClient جاسازی کرده استخراج و out-of-order ارسال می‌کند. Smuggle-SNI از host name تنظیم‌شده یک ClientHello معمولی تازه می‌سازد.
  • sni-blender بدون ساخت transcript دیگر، packet واقعی را در IPv4 fragment و shuffle می‌کند. smuggle-fin اصلاً TLS را inspect نمی‌کند و هنگام نگه داشتن ترافیک عادی یک FIN|ACK مصنوعی reverse را route می‌کند.

overlap-sni​

overlap-sni دو segment واقعی ClientHello را با یک transcript ساخته‌شده روی مسیر عادی upstream جایگزین می‌کند. ابتدا ابتدای واقعی ClientHello، سپس یک TCP SYN جعلی، بعد ClientHello generated با host name متفاوت روی همان sequence numberها و در پایان باقی ClientHello واقعی را می‌فرستد. مسیر downstream هیچ packet‌ای را بازنویسی نمی‌کند و فقط FIN/RST را برای cleanup کردن flow متناظر می‌بیند.

در حالی که first-sni به دیده شدن نخست decoy متکی است، overlap-sni، ‏TCP reassembly را هدف می‌گیرد. split view موردنظر این است که مقصد نخستین بایت‌های رسیده برای range هم‌پوشان را نگه دارد، اما دستگاه میانی SYN جعلی را flow تازه بداند یا بایت‌های پیشین را با overlap دیرتر جایگزین کند و host name ساخته‌شده را parse کند. handling مربوط به TCP overlap و state machineهای middleboxها متفاوت است؛ بنابراین هیچ‌یک از این view‌ها تضمین نمی‌شود.

هشدار

این trick یکی از تهاجمی‌ترین گزینه‌های node است. مرزهای TCP segment سمت client را تغییر می‌دهد، داخل flow برقرار یک SYN خارج از handshake تزریق می‌کند، ترافیک بعدی upstream را delay می‌دهد و بعضی layoutهای ClientHello را عمداً block می‌کند. آن را با packet captureهای نماینده و روی مسیر واقعی آزمایش کنید.

گزینهنوعپیش‌فرضتوضیح
overlap-snistring با طول 1 تا 255 بایتاجباری برای فعال‌سازیHost name مربوط به ClientHello generated. مقدار طولانی‌تر startup node را متوقف می‌کند، چون TlsClient داخلی limit برابر 255 بایت دارد.
overlap-sni-delay-msinteger برحسب ms، با مقدار >= 00تأخیر میان جفت real-prefix/fake-SYN که فوراً ارسال می‌شود و packet X یعنی ClientHello generated. continuationهای واقعی با فاصله ثابت 2 ms دنبال می‌شوند.
overlap-sni-hold-timeout-msinteger برحسب ms، با مقدار > 050بیشترین زمان انتظار برای segment contiguous بلافاصله بعدی که ClientHello انتخاب‌شده را کامل می‌کند؛ expiry، segment held را بدون تغییر آزاد می‌کند.
overlap-sni-syn-ttlinteger از 0 تا 255کپی TTL از packet نگه‌داشته‌شدهTTL مربوط به IPv4 فقط برای TCP SYN جعلی. مقدار 0 یک override واقعی است؛ برای کپی TTL از segment held، فیلد را حذف کنید.

این trick به next عادی در سطح اصلی نیاز دارد.

hello generated توسط TlsClient داخلی ساخته می‌شود و از client واقعی کپی نمی‌شود؛ بنابراین fingerprint همان client داخلی، فهرست ALPN پیش‌فرض (h2، ‏http/1.1) و key share غیر post-quantum را دارد. طول آن را G در نظر بگیرید. host name تنظیم‌شده طولانی‌تر معمولاً G را افزایش می‌دهد، range هم‌پوشان را بزرگ‌تر می‌کند و شرط قرار داشتن SNI واقعی در offset برابر یا بزرگ‌تر از G را دشوارتر می‌سازد. در runtime اگر G بیش از 900 بایت یا بیش از مجموع دو payload واقعی captureشده باشد، generation به‌صورت fail-open شکست می‌خورد.

overlap-sni-delay-ms در یک پنجره absolute پس از transcript نیز مشارکت دارد که برابر max(5000 ms, overlap-sni-delay-ms + 5 ms) است. مقدارهای 0 تا 4995 timing مربوط به packet X را تغییر می‌دهند، اما deadline پنج‌ثانیه‌ای packet‌های بعدی upstream را عوض نمی‌کنند؛ مقدارهای بزرگ‌تر deadline را امتداد می‌دهند. افزایش delay به observer زمان بیشتری برای پردازش SYN جعلی می‌دهد و احتمال سبقت X از prefix واقعی در شبکه را کم می‌کند، اما latency و خطر retransmission یا timeout را بالا می‌برد.

overlap-sni-hold-timeout-ms بهتر است به‌طور محسوسی از TCP retransmission timeout سمت sender کمتر باشد. افزایش آن فقط وقتی کمک می‌کند که segment بعدی ClientHello بیش از 50 ms دیرتر برسد؛ اگر retransmission زودتر برسد، pair به شکل fail-open آزاد می‌شود.

overlap-sni-syn-ttl کم می‌تواند باعث شود دستگاه inspection نزدیک IpManipulator، ‏SYN جعلی را ببیند ولی router بعدی پیش از مقصد آن را expire کند. مقدار قابل‌استفاده به طول مسیر و جایگاه WaterWall بستگی دارد. مقدار 0 معمولاً packet را به link محلی محدود می‌کند. این setting روی هیچ packet دیگری از transcript اثر ندارد.

stateful-flow-limit مشترک، overlap flow table را نیز محدود می‌کند. اگر table نتواند یک SYN آغازین را admit کند، flow دنبال نمی‌شود و بدون تغییر عبور می‌کند.

Flow و packetهای انتخاب‌شده​

flow record فقط با یک SYN آغازین بدون payload ساخته می‌شود: SYN، با ECN اختیاری ECE/CWR، اما بدون ACK، ‏FIN، ‏RST یا payload. پس SYN data مربوط به TCP Fast Open eligible نیست. ترافیک 4-tuple‌ای که این node SYN آغازینش را ندیده است بدون تغییر می‌گذرد. یک SYN آغازین بعدی روی همان tuple، generation تازه‌ای می‌سازد.

  1. Warmup — دو packet نخست upstream بدون تغییر عبور می‌کنند. در اتصال عادی این‌ها خود SYN و ACK تکمیل‌کننده handshake سمت client هستند؛ بنابراین نقطه تصمیم packet سوم upstream، یعنی نخستین data segment سمت client است.
  2. Decision — packet سوم inspect می‌شود. packet بدون payload، payload غیر TLS ClientHello و ClientHello record کامل همگی flow را مستقیماً به passthrough می‌برند و بدون تغییر forward می‌شوند. فقط payload‌ی که با ClientHello شروع شود ولی آن را کامل نکند نگه داشته می‌شود.
  3. Hold — segment داخل IpManipulator retained می‌شود و هیچ چیز از آن ارسال نمی‌گردد. timer با overlap-sni-hold-timeout-ms آن را در صورت ادامه پیدا نکردن flow بدون تغییر آزاد می‌کند.
  4. Pair — packet بعدی upstream باید payload غیرصفر داشته باشد که دقیقاً از held.seq + held.payload_len آغاز شود. دو payload در مجموع باید یک ClientHello قابل parse، شامل host name آن و در یک TLS record، داشته باشند. در صورت success با transcript زیر جایگزین می‌شوند. ACK-only، ‏gap در sequence، overlap/retransmission، record همچنان ناقص، ClientHello پخش‌شده در چند TLS record یا SNI malformed هر دو packet را بدون تغییر آزاد و flow را passthrough می‌کند. trick دقیقاً برای یک completion segment منتظر می‌ماند، نه تعداد نامحدودی از segment‌ها.

چون فقط نخستین ClientHello ناقص hold می‌شود، برای client‌ی که ClientHello آن در یک TCP segment جا می‌گیرد کاری انجام نمی‌دهد. قاعده ordinal packet همچنین یعنی یک packet اضافه client میان handshake ACK و ClientHello می‌تواند حتی در صورت split بودن ClientHello این trick را غیرفعال کند.

Transcript موفق روی wire​

segment held و segment تکمیل‌کننده strictly contiguous اعتبارسنجی می‌شوند: current.seq == held.seq + held.payload_len و هر دو payload غیرصفر دارند؛ سپس به‌طور مجازی در یک payload ترکیب می‌شوند. IpManipulator از TlsClient داخلی ClientHello خودش را برای overlap-sni می‌سازد و host name واقعی را از بایت‌های ترکیب‌شده parse می‌کند.

فرض کنید:

  • S نخستین TCP sequence مربوط به segment held است
  • H طول TCP payload آن segment است
  • R مجموع طول دو payload واقعی است
  • G طول کامل ClientHello generated است
  • O، ‏offset مربوط به host name واقعی از آغاز TCP payload ترکیب‌شده است

transcript موفق به 0 < G <= 900، ‏G <= R و O >= G نیاز دارد. هنگام رسیدن segment واقعی دوم، مسیر عادی upstream موارد زیر را emit یا schedule می‌کند:

  1. packet Y، فوری — بایت‌های واقعی [0, G) با sequence برابر S. header‌های IP/TCP و TCP flags را از packet held کپی می‌کند. این نسخه واقعی range هم‌پوشان است.
  2. SYN جعلی، بلافاصله پس از Y — packet بدون payload روی همان 4-tuple با sequence برابر S - 1، acknowledgement برابر 0 و فقط flag مربوط به SYN. سایر فیلدهای header IPv4/TCP از packet held کپی می‌شوند، از جمله TCP header length، ‏option‌ها و window؛ فقط length، ‏sequence، ‏acknowledgement، ‏flags، identification، ‏TTL اختیاری و checksum‌ها عوض می‌شوند.
  3. packet X، پس از overlap-sni-delay-ms — ClientHello generated با طول دقیقاً G و sequence برابر S. همان interval مربوط به packet Y را می‌پوشاند، اما host name تنظیم‌شده و fingerprint مربوط به TlsClient داخلی را دارد.
  4. Continuation واقعی، با فاصله‌های 2 ms پس از X — اگر G < H باشد، یک packet بایت‌های واقعی [G, H) را در S + G حمل می‌کند و سپس packet‌ای با segment تکمیل‌کننده در sequence اصلی S + H می‌آید. اگر G >= H باشد، فقط بخش لازم باقیمانده از segment تکمیل‌کننده، یعنی بایت‌های واقعی [G, R)، در S + G فرستاده می‌شود؛ بایت‌های زودتر آن قبلاً داخل Y قرار گرفته‌اند.

همه packet‌ها با checksum محاسبه‌شده مجدد خارج می‌شوند و IPv4 identification افزایشی خودشان را دارند: Y از identification مربوط به packet held استفاده می‌کند، سپس fake SYN، ‏X و هر continuation آن را یکی‌یکی افزایش می‌دهند. Y و X از flags مربوط به packet held استفاده می‌کنند. وقتی continuation مربوط به segment held پیش از continuation مربوط به segment تکمیل‌کننده قرار می‌گیرد، اولی فقط CWR، ‏ECE، ‏URG و ACK را نگه می‌دارد و packet آخر flags مربوط به segment تکمیل‌کننده را دارد.

دو packet اصلی recycle می‌شوند و هرگز روی wire ظاهر نمی‌شوند. بسته به اینکه G وارد segment تکمیل‌کننده شود یا نه، replacement چهار یا پنج packet دارد. scheduling محلی ترتیب بالا را حفظ می‌کند، اما شبکه هنوز ممکن است packet‌ها را drop یا reorder کند. delay تنظیم‌شده تنها separation قابل تنظیم است؛ فاصله 2 ms continuationها ثابت است.

آنچه هر participant می‌بیند​

  • مقصد: انتظار می‌رود ابتدا packet Y را بپذیرد و بایت‌های واقعی آن را نگه دارد. اگر SYN جعلی به endpoint TCP برقرار برسد، stackهای مبتنی بر standard معمولاً آن را drop و challenge یا duplicate ACK می‌فرستند؛ stackها، firewallها و load balancerهای دیگر ممکن است اتصال را reset یا مختل کنند. اگر اتصال برقرار بماند، receiver با سیاست first-arrival-wins، ‏X را duplicate متعارض برای [S, S + G) می‌داند و continuation stream واقعی را کامل می‌کند. در این حالت TLS layer همان ClientHello اصلی را می‌گیرد.
  • Middlebox دارای reassembly: در Y یک prefix واقعی ناقص دریافت می‌کند؛ شرط O >= G تضمین می‌کند host name واقعی داخل آن prefix نباشد. اگر SYN جعلی state مربوط به flow را reset کند، X یک ClientHello کامل با host name تنظیم‌شده خواهد بود. دستگاهی که reset نمی‌کند ولی بایت‌های دیرتر را در overlap ترجیح می‌دهد نیز ممکن است به همین نتیجه برسد. بایت‌های واقعی بعدی از انتهای X یا پس از آن شروع می‌شوند و پس از دسته‌بندی hello generated ممکن است نادیده گرفته شوند.
  • client: دو segment اصلی را فرستاده، اما IpManipulator آن‌ها را suppress و transcript جایگزین را به‌جای آن منتشر کرده است. client ممکن است duplicate یا challenge ACK و reset ناشی از SYN جعلی دریافت کند. اگر server بایت‌های واقعی reconstructed را acknowledge نکند، client ممکن است segment‌های اصلی ClientHello را retransmit کند.

split view موردنظر به رسیدن Y پیش از X به server، پایان ندادن state سمت server توسط SYN جعلی و متفاوت بودن policy مربوط به middlebox با server وابسته است. loss، ‏reordering مسیر، ‏TCP normalization، ‏SYN proxy و دستگاه‌هایی که receive window را validate می‌کنند همگی می‌توانند تکنیک را بی‌اثر کنند. به‌ویژه اگر X پیش از Y به مقصد برسد، مقصد ممکن است به‌جای بایت‌های واقعی، بایت‌های generated را بپذیرد و transcript موردنظر TLS شکل نگیرد.

Flowهای blocked​

implementation لازم می‌داند hello generated حداکثر در ابتدای host name واقعی پایان یابد: O >= G. به این ترتیب SNI واقعی کاملاً بیرون از range مبهم و داخل continuation واقعی بعدی قرار می‌گیرد. اگر O < G باشد، trick به forwarding عادی fallback نمی‌کند؛ نام واقعی و هر دو offset را log می‌کند، packet held و جاری را drop می‌کند و flow را blocked علامت می‌زند.

flow blocked همه packet‌های بعدی upstream را drop می‌کند. FIN یا RST upstream record را حذف می‌کند ولی خودش هم drop می‌شود؛ FIN یا RST downstream بدون تغییر forward می‌شود و record را هم حذف می‌کند. IpManipulator resetی برای توضیح حذف داده به client inject نمی‌کند؛ نتیجه معمول stall اتصال و سپس timeout سمت client یا close آغازشده از server/path است.

این انتخاب عمداً fail-closed است، نه fail-open، و خطر اصلی فعال کردن overlap-sni محسوب می‌شود. وقتی hello generated نسبت به hello واقعی بزرگ باشد احتمال آن بیشتر می‌شود. host name تنظیم‌شده طولانی‌تر معمولاً G را افزایش می‌دهد و extension order و padding سمت client واقعی O را تعیین می‌کنند. بسیاری از ClientHelloهای عادی SNI را برای این شرط بیش از حد زود قرار می‌دهند؛ بنابراین پیش از deployment باید packet captureهای نماینده بررسی شوند.

پنجره delay، retransmissionها و packetهای close​

پس از transcript موفق، flow با delay window برابر max(5000 ms, overlap-sni-delay-ms + 5 ms) وارد passthrough می‌شود. تا وقتی این پنجره باز است، packet‌های بعدی upstream شامل ACKها و FIN/RST در FIFO هر flow با سقف 16 packet و 256 KiB queue می‌شوند و همگی در deadline پنجره آزاد می‌گردند. tail scheduleشده transcript همیشه پیش از این FIFO خارج می‌شود. ترافیک downstream هرگز delay نمی‌گیرد.

overflow در FIFO، رسیدن packet flow روی worker دیگر یا ناتوانی در arm کردن timer به‌جای ادامه stall، خروجی pending را به‌ترتیب آزاد می‌کند: ابتدا باقی transcript ساخته‌شده، سپس packet‌های queued و بعد packet جاری. بنابراین در flow شلوغ، limit FIFO می‌تواند پنجره اسمی پنج‌ثانیه‌ای را زودتر تمام کند.

FIFO همچنین retransmission‌های TCP پس از ساخته شدن transcript را می‌گیرد. اما retransmission یا ACK-only که هنگام hold اولیه segment برسد، pair دوpacketی لازم را می‌شکند، segment held و سپس packet جاری را بدون تغییر آزاد می‌کند و flow را passthrough می‌سازد.

مسیر downstream هیچ بایت packet‌ای را عوض نمی‌کند. FIN یا RST downstream در hold اولیه، segment upstream held را بدون تغییر آزاد و flow را حذف می‌کند. در post-transcript delay window، flow را حذف و continuation ساخته‌شده و packet‌های queued upstream باقی‌مانده را cancel می‌کند؛ خود close packet downstream بدون تغییر ادامه می‌یابد. FIN یا RST upstream در delay window پشت transcript ساخته‌شده و packet‌های queued قدیمی‌تر باقی می‌ماند.

مواردی که overlap-sni کاری انجام نمی‌دهد​

در شرایط زیر trick به‌صورت fail-open segment held را آزاد و ترافیک را بدون تغییر forward می‌کند:

  • این node SYN آغازین flow را ندیده باشد
  • packet از نوع IPv4/TCP نباشد یا fragment مربوط به IPv4 باشد
  • packet سوم payload نداشته باشد، TLS ClientHello نباشد یا از قبل یک ClientHello record کامل داشته باشد
  • segment تکمیل‌کننده در overlap-sni-hold-timeout-ms نرسد
  • packet بلافاصله بعدی با segment held از نظر sequence contiguous نباشد، payload نداشته باشد یا هنوز ClientHello تک-record پشتیبانی‌شده را کامل نکند
  • بایت‌های ترکیب‌شده به شکل ClientHello دارای host-name entry parse نشوند
  • TlsClient داخلی نتواند hello بسازد، hello generated بیش از 900 بایت یا طولانی‌تر از payload واقعی ترکیب‌شده باشد
  • packet‌های replacement قابل ساخت نباشند، hold timer arm نشود، flow table نتواند SYN آغازین را admit کند یا state retained میان worker‌ها جابه‌جا شود

حالت O < G در بخش «flow‌های blocked» استثنای عمدی است: fail-closed می‌شود و دو packet واقعی انتخاب‌شده را آزاد نمی‌کند.

تفاوت overlap-sni با سایر trickهای SNI​

  • first-sni، ‏ClientHello خود client را copy می‌کند، host name را عوض می‌کند و decoy را پیش از packet واقعی دست‌نخورده می‌فرستد. می‌تواند روی ClientHello تک‌segment‌ی کار کند و با TTL یا sequence تصادفی تلاش می‌کند decoy را از server دور نگه دارد؛ SYN جعلی handshake تزریق نمی‌کند.
  • smuggle-sni تا 16 segment از ClientHello را capture می‌کند، segment‌های واقعی را از branch کمکی و بعد کپی generated را با حفظ segmentation captureشده روی branch عادی می‌فرستد. overlap-sni دقیقاً دو segment ClientHello را می‌پذیرد و هر دو view را روی همان مسیر عادی می‌گذارد.
  • synfin-sni نیز با ClientHello دوsegmentی شروع می‌شود، اما FIN|ACK یا RST|ACK ساخته‌شده، ‏SYN جعلی، hello generated و filler شبیه TLS را در یک transcript فوری اضافه می‌کند. overlap-sni close یا filler مصنوعی ندارد و به overlap دقیق Y/X همراه delay قابل تنظیم متکی است.
  • sni-blender بدون ساخت host name دیگر، packet واقعی را در لایه IPv4 fragment و reorder می‌کند. ech-sni-trick، ‏ClientHello داخلی جعلی را که از قبل TlsClient جاسازی کرده reorder می‌کند؛ packet X این بخش را نمی‌سازد.

محدودیت‌ها و عوارض جانبی​

  • فقط packet‌های کامل و fragmentنشده IPv4 TCP eligible هستند. IPv6 و fragment‌های IPv4 بدون تغییر عبور می‌کنند.
  • next عادی در سطح اصلی لازم است. همه packet‌های crafted از همان مسیر می‌روند و helper branch اختصاصی overlap وجود ندارد.
  • hello generated از fingerprint و ALPN پیش‌فرض TlsClient داخلی استفاده می‌کند، نه fingerprint client واقعی. فقط packet generated نام تنظیم‌شده را دارد و بایت‌های ClientHello واقعی هرگز بازنویسی نمی‌شوند.
  • مرزهای دو segment اصلی client و IPv4 identification مربوط به segment تکمیل‌کننده حفظ نمی‌شوند. replacement چهار یا پنج packet با header‌های کپی‌شده، identificationهای پشت‌سرهم IPv4، checksum‌های معتبر محاسبه‌شده مجدد و spacing ثابت 2 ms در tail دارد.
  • SYN جعلی روی اتصال برقرار یک protocol violation است و ممکن است normalize، challenge، ‏drop یا با reset پاسخ داده شود. overlap-sni-syn-ttl کم می‌تواند آن را از server دور نگه دارد، ولی تفسیر دستگاه نزدیک‌تر را تضمین نمی‌کند.
  • هنگامی که packet یا delay barrier retained است، packet‌های flow باید روی یک worker مربوط به WaterWall باقی بمانند. mismatch worker warning می‌دهد و fail-open می‌شود.
  • flow record‌ها پس از 20 دقیقه عدم فعالیت expire می‌شوند و با stateful-flow-limit محدودند. استفاده دوباره tuple یک generation تازه می‌سازد تا timer قدیمی داده‌ای را داخل flow جایگزین آزاد نکند.
  • این trick نمی‌تواند در یک instance از IpManipulator با stateful SNI دیگر، sni-blender، ‏packet-duplicate یا action مربوط به TCP bit در upstream مشترک باشد. برای packet shaping سازگار دیگر از node جداگانه استفاده کنید.

مثال:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"overlap-sni": "www.example.com",
"overlap-sni-delay-ms": 5,
"overlap-sni-hold-timeout-ms": 50,
"overlap-sni-syn-ttl": 6
},
"next": "next-packet-node"
}

ech-sni-trick​

ech-sni-trick با TlsClient هماهنگ می‌شود تا پیش از آزاد شدن ClientHello واقعی outer، یک ClientHello از نوع decoy به شکل TCP segment خارج از ترتیب در معرض دید قرار گیرد. این trick نه ECH را encrypt می‌کند و نه ClientHello واقعی را بازنویسی می‌کند. data transformation فقط در upstream است؛ مسیر downstream بایت‌های packet را دست‌نخورده می‌گذارد و فقط TCP FIN یا RST را برای cancel کردن state retained مشاهده می‌کند.

گزینهنوعپیش‌فرضتوضیح
ech-sni-trickstring با طول 1 تا 255 بایتاجباری برای فعال‌سازیHost nameای که باید در ClientHello جاسازی‌شده decoy وجود داشته باشد. همان byte string را در TlsClient متناظر تنظیم کنید.
data-shard-1-delayinteger برحسب ms، با مقدار >= 00زمان میان ارسال decoy خارج از ترتیب و آزاد کردن original packet شماره 1.
data-shard-2-delayinteger برحسب ms، با مقدار >= 00زمان اضافه پس از original packet شماره 1 تا آزاد کردن originalهای 2 تا N با هم و به‌ترتیب. در capture تک‌packet‌ای نادیده گرفته می‌شود.
stateful-flow-limitinteger از 32 تا 104857665536setting مشترکی که ECH flow table را محدود می‌کند؛ بخش «جدول‌های Stateful flow» را ببینید.

مقدار ech-sni-trick به‌صورت byte-for-byte و case-sensitive مقایسه می‌شود و هیچ normalization مربوط به DNS name انجام نمی‌گیرد. تغییر آن فقط در IpManipulator باعث mismatch در runtime و fail-open شدن ClientHello captureشده بدون تغییر می‌شود. تغییر آن در هر دو node، ‏SNI مربوط به decoy را عوض می‌کند و معمولاً طول ClientHello جاسازی‌شده، جایگاهش داخل outer hello و اندازه packet خارج از ترتیب را نیز تغییر می‌دهد.

دو delay نسبی هستند. اگر decoy در زمان T خارج شود، original packet شماره 1 در T + data-shard-1-delay و originalهای 2 تا N در T + data-shard-1-delay + data-shard-2-delay خارج می‌شوند. مجموع آن‌ها باید در بازه millisecond بدون علامت 32 بیتی پشتیبانی‌شده جا شود. افزایش delay نخست به observer مبتنی بر packet زمان بیشتری می‌دهد تا پیش از رسیدن sequence‌های پایین‌تر decoy را پردازش کند. افزایش delay دوم، نخستین shard اصلی را از بقیه جدا می‌کند. هر دو انتخاب reordering مربوط به TCP و latency مربوط به TLS handshake را طولانی می‌کنند. در مقدار 0، ترتیب send همان است اما packet‌ها پشت سر هم خارج می‌شوند.

این trick به next عادی در سطح اصلی نیاز دارد و setting مربوط به TTL، sequence تصادفی، packet count یا helper branch ندارد.

نمونه setting‌های IpManipulator:

{
"name": "ech-packet-shaper",
"type": "IpManipulator",
"settings": {
"ech-sni-trick": "cover.example",
"data-shard-1-delay": 10,
"data-shard-2-delay": 10
},
"next": "next-packet-node"
}

TlsClient سازنده این flow باید همان مقدار decoy را داشته باشد، در حالی که sni عادی آن نام مقصد است:

{
"sni": "origin.example",
"ech-sni-trick": "cover.example"
}

هدف trick​

این trick دستگاه‌های inspectionای را هدف می‌گیرد که TLS record داخل یک TCP segment منفرد را parse می‌کنند، یا پیش از رسیدن sequence range پایین‌تر و مفقود، از داده out-of-order تصمیم زودهنگام می‌گیرند. یک ClientHello کامل decoy را با TCP sequence معتبر اما مربوط به آینده، پیش از بقیه روی wire قرار می‌دهد. سپس ClientHello عادی outer می‌آید و همان byte stream‌ای باقی می‌ماند که endpoint صحیح TCP/TLS دریافت می‌کند.

برخلاف overlap trick، segment زودهنگام با stream نهایی تعارض ندارد. این segment کپی بایت‌هایی است که در همان sequence interval داخل outer ClientHello قرار دارند. بنابراین TCP reassembler سخت‌گیر فقط ClientHello اصلی outer را reconstruct می‌کند. view متفاوت موردنظر فقط در observerی ایجاد می‌شود که segment اولیه را به‌تنهایی parse کند یا SNI آن را پیش از تکمیل reassembly به خاطر بسپارد. این رفتار به مسیر و middlebox وابسته است و تضمین نمی‌شود.

نحوه قرار گرفتن decoy داخل outer ClientHello​

feature دو بخش هماهنگ دارد:

  1. TlsClient.settings.ech-sni-trick یک TLS ClientHello record کامل و جداگانه با SNI مربوط به decoy تنظیم‌شده می‌سازد. همان فهرست ALPN تنظیم‌شده برای TlsClient و همان ترتیب را استفاده می‌کند، اما key share مربوط به x25519mlkem768 را برای این decoy غیرفعال می‌کند تا کوچک‌تر بماند.
  2. TlsClient بایت‌های خام آن record را بدون encryption داخل payload مربوط به یک extension از نوع GREASE encrypted_client_hello در ClientHello واقعی outer می‌گذارد. SNI واضح outer همان TlsClient.settings.sni باقی می‌ماند.
  3. IpManipulator، ‏outer ClientHello را capture می‌کند، record جاسازی‌شده را پیدا می‌کند و در TCP segment زودهنگام copy می‌نماید. هرگز decoy را generate نمی‌کند، هیچ‌یک از SNIها را بازنویسی نمی‌کند و هیچ بایتی را از outer hello حذف نمی‌کند.
هشدار

این سازوکار ECH confidentiality مبتنی بر standard نیست. extension شکل GREASE ECH دارد، اما payload آن plaintext قابل‌شناسایی ClientHello است، نه ECH ciphertext معتبر. هم SNI واقعی outer و هم SNI مربوط به decoy به شکل بایت‌های واضح روی wire قابل مشاهده‌اند.

رفتار موردانتظار مقصد این است که وقتی ECH configuration متناظر ندارد، با outer ClientHello ادامه دهد؛ اما server‌ها، proxyها و security deviceها نسبت به payload غیرمعمول GREASE به‌طور یکسان tolerant نیستند.

Eligibility مربوط به flow و capture کردن ClientHello​

این trick sequence-aware است، نه وابسته به ordinal packet:

  1. یک SYN آغازین بدون payload، ‏flow record را می‌سازد. ECE و CWR می‌توانند برای ECN negotiation جداگانه یا با هم وجود داشته باشند؛ ACK، ‏FIN، ‏RST، هر flag دیگر یا payload مربوط به TCP Fast Open، opener را ineligible می‌کند. اگر node SYN آغازین را ندیده باشد، packet‌های بعدی tuple بدون تغییر عبور می‌کنند.
  2. packet‌های upstream بدون payload، شامل ACK نهایی TCP handshake و ACKهای میان segment‌های ClientHello، فوراً عبور می‌کنند و capture slot مصرف نمی‌کنند.
  3. نخستین payload غیرخالی upstream باید با TLS handshake record و prefix مربوط به ClientHello قابل‌شناسایی آغاز شود. ClientHello کامل تک‌packet‌ای فوراً قابل استفاده است؛ prefix جزئی قابل‌شناسایی capture را شروع می‌کند؛ نخستین payload نامرتبط این flow را تا رسیدن SYN آغازین generation بعدی دائماً passthrough می‌کند.
  4. data segment‌های بعدی فقط وقتی retained می‌شوند که هرکدام دقیقاً از TCP sequence موردانتظار بعدی شروع شوند. capture با کامل شدن نخستین TLS record اعلام‌شده متوقف می‌شود. ClientHello پخش‌شده در چند TLS record پشتیبانی نمی‌شود. اگر TCP segment تکمیل‌کننده بایت‌هایی پس از آن record نیز داشته باشد، کل segment اصلی retained و بعداً replay می‌شود، هرچند فقط record نخست inspect خواهد شد.

حداکثر 16 packet اصلی می‌توانند شرکت کنند و TLS record اعلام‌شده با header پنج‌بایتی آن نمی‌تواند از 16384 بایت بیشتر باشد. capture timeout عدم فعالیت 1500 ms دارد که با data معتبر یا exact retransmission refresh می‌شود. exact retransmission مربوط به packet retained به شکل duplicate drop می‌شود و capture ادامه پیدا می‌کند. gap، segment out-of-order، overlap جزئی یا متعارض، layout پشتیبانی‌نشده TLS، resource failure، عبور از limit packet/size، timeout یا mismatch در worker affinity به‌صورت fail-open عمل می‌کند: originalهای retained پیش از packet جاری forward می‌شوند و ادامه flow عادی می‌گذرد.

پس از capture، ‏IpManipulator همه موارد زیر را لازم دارد:

  • outer ClientHello قابل parse با نخستین entry قابل‌استفاده host_name در SNI
  • extension از نوع outer-form encrypted_client_hello که از نظر syntax قابل parse باشد و payload field محدود و معتبر داشته باشد
  • دقیقاً یک TLS ClientHello record کامل در هر جای آن payload که نخستین SNI host name آن byte-for-byte با مقدار ech-sni-trick تنظیم‌شده برابر باشد

candidateهای ClientHello نامنطبق شمرده نمی‌شوند. دو candidate منطبق ambiguous هستند و fail-open می‌شوند. نبود match، candidate malformed یا truncated، ‏outer hello بدون SNI یا ECH و mismatch میان setting‌های IpManipulator و TlsClient نیز originalها را بدون انتشار segment decoy آزاد می‌کنند.

Transcript موفق TCP​

فرض کنید:

  • S نخستین TCP sequence در packet captureشده است
  • O، ‏offset مربوط به ClientHello decoy از آغاز TCP payload captureشده است
  • L طول کامل ClientHello record مربوط به decoy است

decoy از قبل interval برابر [S + O, S + O + L) را داخل outer ClientHello اشغال می‌کند. در حالت موفق ترتیب روی wire چنین است:

  1. همه packet‌های اصلی ClientHello همچنان held می‌مانند. IpManipulator یک packet با sequence برابر S + O و payload دقیقاً شامل همان L بایت embedded می‌سازد. اگر record جاسازی‌شده از مرز packet‌های اصلی عبور کرده باشد، این کپی همچنان آن را در یک TCP segment coalesce می‌کند.
  2. آن packet فوراً ارسال می‌شود. template مربوط به header IPv4 و TCP از data segment captureشده‌ای می‌آید که offset برابر O را می‌پوشاند؛ شامل address، port، acknowledgement، ‏window، ‏TCP options، ‏TTL و IPv4 identification. طول و sequence اصلاح و checksum‌ها دوباره محاسبه می‌شوند. flags، مقادیر CWR، ‏ECE، ‏URG و ACK را از template حفظ می‌کنند، ‏PSH را می‌افزایند و SYN، ‏FIN و RST را حذف می‌کنند. استفاده دوباره از identification مربوط به template یعنی decoy و یکی از originalها ممکن است IPv4 ID یکسان داشته باشند.
  3. پس از data-shard-1-delay، ‏original packet شماره 1 با مرز payload، ‏sequence و TCP flags اصلی خود آزاد می‌شود.
  4. اگر بیش از یک packet captureشده باشد، IpManipulator به‌اندازه data-shard-2-delay اضافه صبر می‌کند و original packet‌های 2 تا N را پشت سر هم و به‌ترتیب capture آزاد می‌کند. در این گروه دوم delay بین packet‌ها نیست.

پس زمان‌های اسمی send برابر T، ‏T + delay1 و T + delay1 + delay2 هستند. ClientHello تک‌packet‌ای فقط از delay نخست استفاده می‌کند. با هر دو setting برابر 0، ترتیب callback هنوز decoy را اول قرار می‌دهد، اما همه packet‌ها پشت سر هم خارج می‌شوند. اگر release timer قابل schedule نباشد، originalهای مربوط فوراً flush می‌شوند تا stranded نمانند.

originalهای retained توسط این trick بازنویسی نمی‌شوند. بایت‌های payload آن‌ها در [S + O, S + O + L) با کپی اولیه برابرند؛ بنابراین مقصد لازم نیست میان محتوای متعارض overlap انتخاب کند. featureهای سازگار final-egress در همان node، مثل protocol swap یا port ghost، پس از ساخت transcript اجرا می‌شوند و می‌توانند wrapper روی wire را تغییر دهند یا طبق بخش خودشان segmentation انجام دهند.

آنچه client، ‏middlebox و server ممکن است ببینند​

  • client یک outer ClientHello عادی می‌فرستد و نمی‌داند IpManipulator آن را retained و کپی یک range داخلی را inject کرده است. segment زودهنگام با sequence بالاتر ممکن است باعث شود مقصد duplicate ACK برای S و در صورت negotiation شدن SACK، یک SACK block برای future range دریافت‌شده برگرداند. این response‌ها می‌توانند loss recovery سمت client را تغییر دهند، هرچند بایت‌های injectشده با داده‌ای که خود client submit کرده برابرند.
  • inspector مبتنی بر packet، پیش از دیدن outer hello با sequence پایین‌تر، یک TCP payload می‌بیند که با ClientHello کامل و SNI decoy تنظیم‌شده شروع می‌شود. دستگاهی که payload‌های out-of-order را parse می‌کند یا نخستین SNI را latch می‌کند ممکن است flow را از این decoy دسته‌بندی کند. stream reassembler سخت‌گیر منتظر gap می‌ماند، outer ClientHello اصلی را reconstruct می‌کند و می‌تواند SNI واقعی outer را ببیند؛ دستگاهی که هر دو را بررسی کند شاید verdict اولیه را نادیده بگیرد، جایگزین کند یا penalize نماید.
  • TCP stack مقصد ممکن است segment اولیه را به‌صورت data out-of-order buffer کند، با SACK acknowledge کند یا براساس receive-window/normalization policy دور بیندازد. پس از پر شدن gap توسط originalها، stack tolerant همان بایت‌های outer ClientHello ساخته‌شده توسط TlsClient را reconstruct می‌کند. انتظار می‌رود TLS endpoint از SNI outer استفاده و GREASE ECH غیرقابل‌استفاده را سازگارانه مدیریت کند. TCP normalizer، ‏SYN proxy، ‏TLS gateway و server آگاه به ECH ممکن است رفتار دیگری داشته باشند.

delayها برای تصمیم زودهنگام middlebox زمان فراهم می‌کنند، اما تصمیم را تحمیل نمی‌کنند. مقدارهای بزرگ‌تر latency مربوط به handshake و مدت TCP reordering را افزایش می‌دهند. مقدار نزدیک به retransmission timeout سمت sender یا timeout application می‌تواند به‌جای بهبود trick اتصال را از کار بیندازد.

Retransmissionها، ترافیک بعدی و بسته‌شدن اتصال​

پس از انتشار decoy، فقط exact retransmission مربوط به original segment‌ی که هنوز pending است—با sequence، ‏payload length و payload bytes یکسان—swallow می‌شود. ACK-only، ‏application data بعدی یا overlap جزئی/مبهم فوراً forward می‌گردد. داده بعدی پشت دو release timer queue نمی‌شود، بنابراین ممکن است خودش از ClientHello held سبقت بگیرد و data out-of-order بیشتری بسازد. پس از release شدن original، retransmission‌های آن دیگر swallow نمی‌شوند.

close handling در جایی که close عادی upstream هنوز معنا دارد، ترتیب TCP را حفظ می‌کند:

  • FIN upstream هنگام capture ناقص، originalهای captureشده را پیش از FIN آزاد می‌کند. هنگام delayed release، همه originalهای pending را پیش از FIN flush می‌کند.
  • RST upstream هنگام delayed release، originalهای pending را دور می‌اندازد و سپس reset را عبور می‌دهد. هنگام capture ناقص، originalهای held پیش از reset packet آزاد می‌شوند.
  • FIN یا RST downstream بدون تغییر عبور می‌کند، اما flow record را حذف، capture ناقص را دور و originalهایی را که هنوز release نشده‌اند cancel می‌کند.

flow record‌ها با TCP four-tuple و generation اتصال key می‌شوند. SYN آغازین معتبر تازه روی tuple استفاده‌شده مجدد، capture و release timer‌های قبلی را پیش از شروع flow جایگزین invalid می‌کند؛ بنابراین بایت قدیمی ClientHello داخل آن inject نمی‌شود. پس از release، record تا بسته‌شدن، جایگزینی یا expire شدن بعد از 20 دقیقه inactivity در passthrough می‌ماند.

تفاوت ech-sni-trick با سایر trickهای SNI​

  • first-sni یک یا چند کپی کامل از outer ClientHello سمت client می‌سازد، SNI هرکدام را تغییر می‌دهد و با TTL یا sequence تصادفی تلاش می‌کند decoy‌های متعارض را از server دور نگه دارد. ech-sni-trick از یک decoy از پیش embedded در sequence interval واقعی خودش استفاده می‌کند، setting مربوط به TTL یا random sequence ندارد و بایت متعارضی نمی‌سازد.
  • smuggle-sni، ‏ClientHello واقعی را روی helper branch و decoy generated را پس از delay روی branch عادی می‌فرستد. ech-sni-trick به helper نیاز ندارد و هر دو view را روی مسیر عادی قرار می‌دهد.
  • overlap-sni و synfin-sni مرزهای segment captureشده را با sequence range‌های عمداً متعارض جایگزین و SYN جعلی و packet close یا filler تزریق می‌کنند. ech-sni-trick control packet مربوط به TCP ندارد و segmentation اصلی را replay می‌کند.
  • sni-blender، ‏ClientHello واقعی را در لایه IPv4 fragment و reorder می‌کند. ech-sni-trick fragment IPv4 نمی‌سازد؛ یک TCP data segment اضافه را از payload مربوط به GREASE ECH کپی می‌کند.

محدودیت‌ها و عوارض جانبی​

  • فقط packet‌های کامل و fragmentنشده IPv4 TCP eligible هستند. IPv6 و fragment‌های IPv4 بدون تغییر عبور می‌کنند.
  • next عادی در سطح اصلی لازم است. IpManipulator باید SYN آغازین را ببیند و نخستین payload غیرخالی client باید با layout پشتیبانی‌شده ClientHello تک-record آغاز شود.
  • decoy همراه header‌های کپی‌شده IP/TCP باید در mtu جا شود. decoy بزرگ به‌جای split شدن توسط ECH trick به‌صورت fail-open رد می‌شود. SNI طولانی‌تر می‌تواند outer record و این packet را بزرگ‌تر کند.
  • flow table با stateful-flow-limit محدود است و flow پذیرفته‌نشده بدون تغییر عبور می‌کند. captureهای ناقص TLS از slot pool محدود مشترک نیز استفاده می‌کنند؛ در فشار بالا یک capture ناقص قدیمی ممکن است آزاد و passthrough شود.
  • تمام packetهای retained یک flow باید روی یک worker مربوط به WaterWall بمانند. خود IpManipulator پیش از پردازش stateful، ‏affinity مربوط به packet line را برقرار می‌کند؛ mismatch باقی‌مانده در worker affinity همچنان fail-open می‌شود.
  • packet زودهنگام IPv4 identification مربوط به template را duplicate می‌کند و ممکن است duplicate ACK، ‏SACK، heuristicهای retransmission، rule مربوط به traffic normalizer یا anomaly detection را trigger کند. delayها memory را هم نگه می‌دارند و TLS handshake را عقب می‌اندازند.
  • این ECH واقعی نیست و SNI را confidential نمی‌کند: SNI واقعی در outer ClientHello و SNI decoy داخل GREASE payload قابل مشاهده‌اند.
  • این trick نمی‌تواند با stateful SNI دیگر، sni-blender، ‏packet-duplicate یا action مربوط به TCP bit در upstream داخل یک IpManipulator مشترک باشد. operationهای سازگار که باید packet‌های emitted را shape کنند در IpManipulator بعدی قرار می‌گیرند.

synfin-sni​

synfin-sni یک TCP overlap trick از نوع stateful و فقط-upstream برای TLS ClientHelloای است که دقیقاً در دو data segment انتخاب‌شده split شده باشد. آن دو packet اصلی را suppress می‌کند و transcript جایگزینی شامل بایت‌های واقعی، close مصنوعی، ‏SYN جعلی، ‏ClientHello generated از نوع decoy، filler اختیاری و باقی بایت‌های واقعی منتشر می‌کند. این trick packet‌های downstream را inspect یا تغییر نمی‌دهد.

split view موردنظر هم از state اتصال TCP و هم از سیاست conflicting overlap استفاده می‌کند. مقصدی که نخستین بایت‌های رسیده برای sequence range را نگه دارد می‌تواند ClientHello واقعی را reconstruct کند. دستگاه میانی ممکن است close و SYN بعدی را مرز flow تازه بداند، یا بایت‌های دیرتر overlap را ترجیح دهد و ClientHello generated دارای synfin-sni را parse کند.

هشدار

این trick یکی از تهاجمی‌ترین رفتارهای protocol در node است. عمداً TCP data متعارض، یک close و SYN دیگر را روی four-tuple برقرار می‌فرستد. با TTL، ‏sequence و checksum معتبر پیش‌فرض، packet‌های مصنوعی ممکن است به مقصد برسند و اتصال را close یا reset کنند. آن را با client، ‏server، ‏middlebox و packet captureهای نماینده آزمایش کنید.

گزینهنوعپیش‌فرضتوضیح
synfin-snistring با طول 1 تا 255 بایتاجباری برای فعال‌سازیHost name مربوط به ClientHello generated از نوع decoy. limit مؤثر از TlsClient داخلی می‌آید.
synfin-sni-hold-timeout-msinteger برحسب ms، با مقدار > 050بیشترین زمان انتظار برای segment contiguous بلافاصله بعدی که ClientHello انتخاب‌شده را کامل کند. expiry، segment held را بدون تغییر آزاد می‌کند.
synfin-sni-additional-range-mininteger از 0 تا 655350حداقل extension درخواستی پس از طول hello generated برای overlap مربوط به real-first/fake-filler. اگر maximum تنظیم نشده باشد، همین مقدار maximum نیز می‌شود. clamp ایمنی runtime ممکن است آن را کاهش دهد.
synfin-sni-additional-range-maxinteger از 0 تا 655350، یا minimum تنظیم‌شده وقتی فقط minimum وجود داردحداکثر extension درخواستی overlap. اگر به‌تنهایی تنظیم شود، minimum همان 0 می‌ماند؛ minimum نباید از maximum بزرگ‌تر باشد.
synfin-sni-syn-ttlinteger از 0 تا 255در صورت وجود template، کپی TTL از SYN آغازینTTL مربوط به IPv4 فقط برای SYN جعلی. اگر header کپی‌شده opener در دسترس نباشد، construction به header مربوط به data held fallback می‌کند.
synfin-sni-fin-ttlinteger از 0 تا 255کپی TTL از data packet heldTTL مربوط به IPv4 فقط برای `FIN
synfin-sni-fake-ttlinteger از 0 تا 255کپی TTL از data packet heldTTL مربوط به IPv4 فقط برای packet X یعنی ClientHello کامل generated. روی filler یا packet‌های واقعی اثر ندارد.
synfin-sni-random-syn-checksumbooleanfalseبه‌جای checksum‌های معتبر محاسبه‌شده، مقدارهای random دلخواه داخل فیلدهای checksum مربوط به IPv4 و TCP در fake SYN می‌گذارد.
synfin-sni-random-fin-checksumbooleanfalseهمان arbitrary-checksum را فقط روی close packet مصنوعی اعمال می‌کند.
synfin-sni-random-syn-sequencebooleanfalseبه‌جای S - 1 از sequence تصادفی 32 بیتی برای fake SYN استفاده می‌کند. packet X همچنان در S می‌ماند.
synfin-sni-random-fin-sequencebooleanfalseبه‌جای S + Y یعنی انتهای overlap interval، sequence تصادفی 32 بیتی برای close انتخاب می‌کند.
synfin-sni-use-rstbooleanfalseبرای close مصنوعی به‌جای `FIN

TTL برابر 0 یک override واقعی است. برای حفظ TTL مربوط به template، فیلد TTL را کاملاً حذف کنید. مقدار 0 معمولاً packet routed را از عبور فراتر از link محلی بازمی‌دارد، اما handling دقیق همچنان به مسیر وابسته است.

record generated از TlsClient داخلی با ترتیب ALPN پیش‌فرض h2، ‏http/1.1 و x25519mlkem768 غیرفعال استفاده می‌کند و از fingerprint client واقعی کپی نمی‌شود. طول کامل record را G فرض کنید. generation در runtime به‌صورت fail-open شکست می‌خورد مگر اینکه G حداکثر 900 بایت و حداکثر برابر مجموع دو payload واقعی انتخاب‌شده باشد. synfin-sni طولانی‌تر معمولاً G را افزایش می‌دهد و شرط جایگاه SNI واقعی را که در ادامه می‌آید دشوارتر می‌کند.

additional range پس از clamp کردن دو سر تنظیم‌شده به بازه امنی که payload واقعی و محل SNI اجازه می‌دهند، برای هر flow موفق یک بار انتخاب می‌شود. برای مقدار واقعی انتخاب‌شده A، اگر A < 5 باشد A بایت filler تصادفی ساخته می‌شود؛ اگر A >= 5 باشد یک header مربوط به TLS Application Data record (17 03 03 و record length برابر A - 5) به‌همراه بایت‌های random ساخته می‌شود. بنابراین minimum تنظیم‌شده فقط request است و وقتی safe range کوچک‌تر باشد تضمین نمی‌شود.

کل replacement sequence synchronously و پشت سر هم منتشر می‌شود. hold timeout برای pacing نیست و setting delay دیگری برای SynFIN وجود ندارد. تنظیم مشترک stateful-flow-limit جدول SynFIN را محدود می‌کند و پیش‌فرض آن 65536 است. این trick به next عادی در سطح اصلی نیاز دارد و helper branch ندارد.

مثالی شامل همه کنترل‌ها؛ این مقدارها فقط illustrative هستند و recommendation مستقل از مسیر محسوب نمی‌شوند:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"synfin-sni": "www.example.com",
"synfin-sni-hold-timeout-ms": 50,
"synfin-sni-additional-range-min": 8,
"synfin-sni-additional-range-max": 24,
"synfin-sni-syn-ttl": 5,
"synfin-sni-fin-ttl": 5,
"synfin-sni-fake-ttl": 5,
"synfin-sni-random-syn-checksum": false,
"synfin-sni-random-fin-checksum": false,
"synfin-sni-random-syn-sequence": false,
"synfin-sni-random-fin-sequence": false,
"synfin-sni-use-rst": false
},
"next": "next-packet-node"
}

Flow و packetهای انتخاب‌شده​

record فقط وقتی ساخته می‌شود که این node یک SYN آغازین بدون payload ببیند. ECE و CWR می‌توانند همراه SYN باشند؛ ACK، ‏FIN، ‏RST، هر flag دیگر یا payload مربوط به TCP Fast Open، opener را ineligible می‌کند. packet‌های tuple‌ای که SYN آغازین آن دیده نشده بدون تغییر عبور می‌کنند. SYN آغازین معتبر بعدی روی همان tuple یک generation جایگزین آغاز می‌کند.

  1. Warmup — دو packet نخست upstream که close نیستند بدون تغییر عبور می‌کنند و warmup محسوب می‌شوند. معمولاً این‌ها SYN آغازین و ACK نهایی handshake سمت client هستند؛ SYN|ACK downstream شمرده نمی‌شود. پس packet سوم upstream معمولاً نخستین segment مربوط به ClientHello است.
  2. Decision — اگر packet سوم payload نداشته باشد، با TLS ClientHello قابل شناسایی شروع نشود یا از قبل نخستین TLS record کامل را داشته باشد، فوراً عبور می‌کند و flow passthrough می‌شود. فقط ابتدای ClientHello تک-record‌ی partial یا fragmented نگه داشته می‌شود.
  3. Hold — segment انتخاب‌شده داخل IpManipulator می‌ماند و در مدت timer هیچ بخشی از آن ارسال نمی‌شود. timeout آن را بدون تغییر آزاد می‌کند و flow را passthrough می‌سازد.
  4. Pair — packet بلافاصله بعدی upstream باید payload غیرصفر داشته و دقیقاً در held.seq + held.payload_len آغاز شود. بایت‌های ترکیب‌شده باید ClientHello قابل parse داشته باشند که handshake آن دقیقاً یک TLS record را پر می‌کند و نخستین entry قابل‌استفاده host_name در SNI دارد. ACK، ‏exact retransmission، gap، ‏overlap، segment out-of-order، record همچنان ناقص یا layout malformed، packet held و سپس packet جاری را بدون تغییر آزاد و passthrough را انتخاب می‌کند. trick برای data segment سوم منتظر نمی‌ماند.

نخستین TLS record اعلام‌شده حداکثر می‌تواند 16384 بایت باشد. ClientHello پخش‌شده در چند TLS record پشتیبانی نمی‌شود. اگر TCP segment تکمیل‌کننده بایت‌هایی پس از record انتخاب‌شده داشته باشد، آن بایت‌ها در payload واقعی ترکیب‌شده می‌مانند و در continuation واقعی حفظ می‌شوند.

قاعده ordinal packet سخت‌گیرانه است. ClientHello تک‌segment‌ی هرگز transform نمی‌شود و یک packet اضافه client میان handshake ACK و نخستین segment ClientHello می‌تواند باعث شود packet سوم passthrough را انتخاب کند.

Transcript موفق TCP​

فرض کنید:

  • S نخستین TCP sequence مربوط به segment held است
  • H طول payload آن segment است
  • R مجموع طول دو payload واقعی است
  • G طول ClientHello record generated از نوع decoy است
  • O، ‏offset مربوط به host name واقعی SNI از آغاز TCP payload ترکیب‌شده است
  • A، ‏additional range انتخاب‌شده پس از clamp در runtime است
  • Y = G + A طول interval عمداً هم‌پوشان است

transcript موفق به 0 < G <= 900، ‏G <= R و O >= G نیاز دارد. IpManipulator، ‏additional range را طوری clamp می‌کند که 0 <= A <= min(R - G, O - G). در نتیجه Y <= O است: packet واقعی Y می‌تواند دقیقاً پیش از نخستین بایت SNI واقعی پایان یابد، اما هیچ بایتی از host name واقعی را حمل نمی‌کند.

packet held و تکمیل‌کننده اصلی recycle می‌شوند. پیش از egress segmentation بعدی، پنج تا هفت replacement packet به‌ترتیب زیر و synchronously خارج می‌شوند:

  1. packet واقعی Y — بایت‌های واقعی [0, Y) با sequence برابر S. header IP/TCP و TCP flags دقیق را از packet held کپی می‌کند. payload آن می‌تواند از مرز اصلی H عبور و بایت‌هایی از هر دو segment انتخاب‌شده را coalesce کند.
  2. Close مصنوعی — packet header-only از نوع FIN|ACK، یا در صورت فعال بودن synfin-sni-use-rst از نوع RST|ACK. sequence پیش‌فرض آن S + Y است. acknowledgement، ‏window، ‏TCP options، ‏addressها و port‌ها از data packet held می‌آیند.
  3. SYN جعلی — packet header-only فقط با flag مربوط به SYN، acknowledgement برابر 0 و sequence پیش‌فرض S - 1. معمولاً SYN آغازین captureشده را به عنوان header template استفاده می‌کند و TCP header length، ‏options و window همان SYN را نگه می‌دارد. اگر حفظ template شکست خورده باشد، header مربوط به data held استفاده می‌شود.
  4. packet جعلی X — دقیقاً G بایت ClientHello generated در sequence برابر S. flags، مقادیر CWR، ‏ECE، ‏URG و ACK را از segment held حفظ و PSH را اضافه می‌کنند؛ SYN، ‏FIN و RST حذف می‌شوند. X با Y واقعی در interval برابر [S, S + G) تعارض دارد.
  5. Filler جعلی اختیاری — وقتی A > 0 باشد، یک packet interval [S + G, S + Y) را می‌پوشاند. payload آن همان بایت‌های random یا شکل TLS Application Data توضیح‌داده‌شده است و از همان policy مربوط به continuation-plus-PSH در X استفاده می‌کند.
  6. یک یا دو tail واقعی — بایت‌های واقعی [Y, R) stream اصلی را کامل می‌کنند. اگر Y < H باشد، suffix مربوط به segment held در S + Y خارج می‌شود و segment تکمیل‌کننده با sequence اصلی S + H می‌آید. در غیر این صورت فقط suffix استفاده‌نشده segment تکمیل‌کننده در S + Y خارج می‌شود.

Y واقعی با IPv4 identification مربوط به packet held شروع می‌شود. close، fake SYN، ‏X، filler و tailها identificationهای پی‌درپی می‌گیرند؛ پس identification اصلی packet تکمیل‌کننده حفظ نمی‌شود. packet‌های rebuilt معمولاً checksum معتبر IPv4/TCP دارند. وقتی suffix مربوط به segment held پیش از tail دوم می‌آید، suffix نخست فقط CWR، ‏ECE، ‏URG و ACK را نگه می‌دارد؛ tail نهایی flags مربوط به segment تکمیل‌کننده را دارد.

هیچ sleep مصنوعی یا pacing قابل تنظیمی میان این خروجی‌ها نیست. behavior سازگار final-egress در همان node، شامل port ghost، ‏protocol swap و TCP segmentation مبتنی بر MTU، پس از ساخت transcript اجرا می‌شود و می‌تواند wrapper را تغییر دهد یا تعداد نهایی packet‌ها را افزایش دهد.

آنچه هر participant ممکن است ببیند​

  • client: دو segment عادی ClientHello را فرستاده، اما WaterWall آن‌ها را suppress و transcript جایگزین را ارسال کرده است. control‌های مصنوعی و data متعارض می‌توانند duplicate ACK، ‏challenge ACK، تغییر SACK یا reset ایجاد کنند. اگر acknowledgement progress با transmission اصلی client هماهنگ نباشد، TCP stack ممکن است مرزهای segment اصلی را retransmit کند؛ retransmission‌های بعدی پس از موفقیت transcript suppress نمی‌شوند.
  • Middlebox دارای state: ابتدا prefix واقعی را می‌بیند که پیش از host name واقعی تمام می‌شود، سپس close و SYN دیگر را می‌گیرد. با sequence‌های پیش‌فرض، SYN تازه در S - 1 باعث می‌شود X در S شبیه نخستین data در flow جایگزین باشد. دستگاه ممکن است SNI decoy و filler اختیاری شبیه TLS را parse کند. دستگاهی که control‌ها را نادیده می‌گیرد ولی در overlap از last-arrival-wins استفاده می‌کند نیز به همان payload view می‌رسد. reassembler سخت‌گیر با first-arrival-wins، ‏Y را نگه می‌دارد.
  • مقصد: انتظار می‌رود Y واقعی را برای [S, S + Y) نگه دارد، X و filler را duplicateهای متعارض بداند و tail واقعی را append کند تا ClientHello اصلی reconstruct شود. این امر نیاز دارد Y اول برسد و control‌های مصنوعی state سمت مقصد را terminate نکنند. FIN معتبر و in-window در S + Y می‌تواند receive stream را دقیقاً جایی close کند که tail واقعی آغاز می‌شود؛ RST پذیرفته‌شده اتصال را فوراً پایان می‌دهد.

نتیجه موردنظر وابسته به مسیر است. loss، ‏reordering شبکه، policy مربوط به first/last overlap، ‏checksum validation، ‏receive-window validation، ‏TCP normalization، ‏SYN proxy، ‏state مربوط به NAT/firewall و رفتار TCP سمت server همگی می‌توانند نتیجه را عوض کنند. به‌ویژه اگر X پیش از Y به مقصدی با first-arrival-wins برسد، بایت‌های generated می‌توانند TLS stream سمت server را خراب کنند.

اثر settingهای TTL، ‏checksum، ‏sequence و close​

سه setting مربوط به TTL مستقل‌اند. synfin-sni-syn-ttl فقط روی fake SYN، synfin-sni-fin-ttl فقط روی close و synfin-sni-fake-ttl فقط روی X اعمال می‌شود، نه filler. TTL کم می‌تواند packet‌ای را به observer نزدیک نشان دهد و پیش از مقصد expire کند، اما middlebox خاصی را target نمی‌کند و تغییر route مقدار انتخابی را بی‌اعتبار می‌سازد.

دو setting مربوط به random checksum، هر دو فیلد checksum مربوط به IPv4 و TCP را روی control packet منتخب arbitrary می‌کنند. آن‌ها معمولاً نامعتبر خواهند بود، هرچند مقدار random ممکن است اتفاقی درست باشد. classifier passive ممکن است چنین packet‌ای را inspect کند ولی endpoint دارای validation آن را drop می‌کند؛ middlebox دارای validation هم drop خواهد کرد. Y، ‏X، filler و tailهای واقعی checksum معتبر دارند. operation بعدی egress که packet را rebuild می‌کند، مثل port-ghost tailing یا MTU segmentation، ممکن است checksum arbitrary را repair کند؛ اگر bad checksum ضروری است stageهای بعدی را در نظر بگیرید.

synfin-sni-random-syn-sequence فقط fake SYN را جابه‌جا می‌کند. X در S باقی می‌ماند، پس رابطه منسجم پیش‌فرض SYN(S - 1) -> data(S) از دست می‌رود. این می‌تواند control را بیرون از window مقصد قرار دهد، اما ممکن است middlebox sequence-aware را هم از تلقی X به عنوان data مربوط به flow جدید بازدارد. به همین شکل، synfin-sni-random-fin-sequence فقط close را جابه‌جا می‌کند و شاید پذیرش آن توسط مقصد یا باورپذیری آن برای observer sequence-aware را کاهش دهد.

synfin-sni-use-rst بدون تغییر sequence پیش‌فرض S + Y، ‏FIN|ACK را به RST|ACK تبدیل می‌کند. RST پذیرفته‌شده معمولاً از FIN عادی مخرب‌تر است و close نیمه graceful که یک sequence number مصرف می‌کند محسوب نمی‌شود.

افزایش additional range هم coverage مربوط به نخستین arrival در Y واقعی و هم coverage بعدی filler را بزرگ‌تر می‌کند و close و tail واقعی را به S + Y می‌برد. هیچ‌گاه از ابتدای SNI واقعی عبور نمی‌کند. طول و extension order واقعی ClientHello ممکن است حتی minimum تنظیم‌شده را به مقدار کمتر clamp کنند.

رفتار fail-open و blocked​

بیشتر حالت‌های پشتیبانی‌نشده fail-open هستند. packet held و جاری در شرایط زیر بدون transcript ساخته‌شده آزاد یا forward می‌شوند:

  • SYN آغازین دیده نشده، packet از نوع IPv4 TCP کامل نیست یا flow table نمی‌تواند record را admit کند
  • packet سوم payload ندارد، غیر TLS است یا از قبل ClientHello record کامل دارد
  • hold expire می‌شود یا timer آن قابل arm نیست
  • packet بلافاصله بعدی payload ندارد، strictly contiguous نیست یا ClientHello تک-record پشتیبانی‌شده دارای SNI را کامل نمی‌کند
  • hello داخلی قابل generation نیست، ‏G > 900 یا G > R است
  • replacement packet قابل ساخت نیست یا state retained میان worker‌ها جابه‌جا شده است

حالت O < G استثنای عمدی fail-closed است. اگر host name واقعی پیش از پایان record generated شروع شود، IpManipulator mismatch را log می‌کند، هر دو packet واقعی انتخاب‌شده را drop می‌کند و flow upstream را blocked علامت می‌زند. همه packet‌های بعدی upstream drop می‌شوند. FIN یا RST upstream record را حذف می‌کند ولی خودش هم drop می‌شود. SYN آغازین معتبر تازه روی همان tuple، generation جدید را شروع می‌کند. reset محلی برای توضیح failure ارسال نمی‌شود؛ نشانه معمول stall اتصال است.

Retransmissionها، closeها و طول عمر flow​

در hold اولیه، FIN یا RST upstream ابتدا segment held را آزاد می‌کند، سپس close را forward و record را حذف می‌کند. ACK-only یا retransmission‌ی که به‌جای completion موردانتظار برسد، pair دوpacketی را می‌شکند و packet held سپس جاری را بدون تغییر آزاد و flow را passthrough می‌کند.

پس از transcript موفق، flow فوراً passthrough می‌شود. ACKهای بعدی، application data، retransmission‌های ClientHello اصلی و closeها پشت delay window queue نمی‌شوند. بنابراین retransmission بعدی می‌تواند segmentation اصلی client و overlap policy را دوباره روی wire قرار دهد. FIN یا RST upstream، record passthrough را حذف و عادی ادامه می‌یابد.

SynFIN ترافیک downstream، شامل FIN یا RST downstream را inspect نمی‌کند و آن ترافیک record را حذف نمی‌کند. record‌های بدون استفاده پس از 20 دقیقه عدم فعالیت expire می‌شوند. هر pair retained باید روی یک worker مربوط به WaterWall بماند؛ completion میان worker‌ها fail-open می‌شود، اما packet جاری روی worker خودش ادامه می‌دهد و packet held به owner worker بازگردانده می‌شود؛ پس در این error path ترتیب اصلی روی wire تضمین نمی‌شود.

SYN آغازین جایگزین، generationهای old مربوط به hold timer را پیش از شروع دوباره warmup invalid می‌کند. اگر tuple reuse هنگام hold فعال رخ دهد، buffer held قدیمی به‌جای وارد شدن به اتصال جایگزین دور انداخته می‌شود.

تفاوت synfin-sni با سایر trickهای SNI​

  • first-sni، ‏ClientHello واقعی را clone و host name را عوض می‌کند و با TTL یا sequence تصادفی تلاش می‌کند decoy را از server دور نگه دارد. روی ClientHello تک‌segment‌ی هم کار می‌کند و control مربوط به state اتصال تزریق نمی‌کند.
  • smuggle-sni تا 16 segment را capture می‌کند و originalهای واقعی را از helper branch می‌فرستد، در حالی که hello generated پس از delay روی branch عادی قرار می‌گیرد. SynFIN دقیقاً یک segment held و completion contiguous بلافاصله بعدی را می‌پذیرد، helper ندارد و همه view‌ها را پشت سر هم می‌فرستد.
  • overlap-sni از همان انتخاب دوsegmentی و overlap مربوط به real-Y/fake-X استفاده می‌کند، اما فقط fake SYN تزریق می‌کند، طول overlap را در G ثابت نگه می‌دارد و X و tail واقعی را با delay schedule می‌کند. SynFIN پیش از آن FIN|ACK/RST|ACK اضافه می‌کند، می‌تواند overlap را با filler بزرگ‌تر کند، کنترل checksum و sequence دارد و post-transcript delay window ندارد.
  • ech-sni-trick یک کپی byte-identical خارج از ترتیب از decoy جاسازی‌شده توسط TlsClient می‌فرستد و هیچ TCP byte یا control متعارضی نمی‌سازد. sni-blender packet واقعی را در IPv4 fragment و shuffle می‌کند، نه اینکه TCP segmentation را جایگزین یا ClientHello دیگری generate کند.

محدودیت‌ها و عوارض جانبی​

  • فقط packet‌های کامل و fragmentنشده IPv4 TCP eligible هستند. IPv6، ‏fragment‌های IPv4 و SYN data مربوط به TCP Fast Open بدون تغییر عبور می‌کنند.
  • ClientHello باید روی packet سوم upstream آغاز شود، در آن packet ناقص باشد و پس از دقیقاً یک data segment contiguous قابل parse گردد. ClientHelloهای تک‌segment‌ی یا سه‌segment‌ی و بیشتر transform نمی‌شوند.
  • hello generated از fingerprint و ALPN پیش‌فرض TlsClient داخلی استفاده می‌کند، نه fingerprint client واقعی. نام تنظیم‌شده باید 1 تا 255 بایت باشد و G، ‏R و O شروط بالا را برآورده کنند.
  • مرزهای data اصلی و IPv4 identification مربوط به packet تکمیل‌کننده کنار گذاشته می‌شوند. MTU shaping نهایی می‌تواند data rebuilt را دوباره split و تعداد اسمی پنج تا هفت packet را بیشتر کند.
  • close/SYN مصنوعی روی اتصال برقرار ممکن است توسط host، ‏firewall، ‏NAT، ‏load balancer، ‏SYN proxy و intrusion systemها normalize، ‏challenge، ‏rate-limit، drop یا attack تلقی شود.
  • flow table محدود از stateful-flow-limit استفاده می‌کند و فشار admission به شکل fail-open است. hold فعال memory مربوط به packet را تا زمان pair، ‏timeout، close upstream، جایگزینی یا destruction نگه می‌دارد و worker affinity سازگار لازم دارد.
  • next عادی در سطح اصلی لازم است. این trick نمی‌تواند در همان IpManipulator با stateful SNI دیگر، sni-blender، ‏packet-duplicate یا action مربوط به TCP bit در upstream مشترک باشد. packet shaping سازگار بعدی را در node دیگری بگذارید و اثرش روی TTL، ‏checksum، ‏segmentation، ‏tuple و ترتیب packet را در نظر بگیرید.

smuggle-fin​

smuggle-fin یک trick مربوط به ordering با دو branch است. یک data packet عادی را روی مسیر normal نگه می‌دارد، ‏FIN|ACK باورپذیر در جهت reverse را از helper path جداگانه می‌فرستد و منتظر می‌ماند کپی دقیق آن FIN در مسیر downstream برگردد. هدف این است که دستگاه stateful روی helper path پیش از آزاد شدن data اصلی، signal بسته‌شدن اتصال را ببیند. اینکه این signal classification یا connection state را تغییر دهد به topology و خود دستگاه بستگی دارد؛ trick تضمین نمی‌کند middlebox FIN را echo یا accept کند یا براساس آن عمل نماید.

برخلاف نام fin-sni-delay-ms، این trick TLS، ‏ClientHello یا SNI را parse نمی‌کند. هر packet کامل و fragmentنشده IPv4 TCP که ACK داشته باشد، دست‌کم یک بایت TCP payload حمل کند و هیچ‌یک از SYN، ‏FIN یا RST را نداشته باشد، می‌تواند trigger شود. flag‌هایی مانند PSH، ‏ECE، ‏CWR و URG packet eligible را رد نمی‌کنند. IPv6، ‏fragment‌های IPv4، packet‌های غیر TCP، ‏ACK-only و SYN data مربوط به TCP Fast Open بدون شروع trick عبور می‌کنند.

گزینهنوعپیش‌فرضتوضیح
smuggle-finbooleanfalsetrick را فعال می‌کند. این setting host name یا TLS selector نیست.
fin-sni-delay-msinteger برحسب ms، با مقدار >= 00delay پس از consume شدن `FIN
fin-pause-timeout-msinteger برحسب ms، با مقدار > 01000حداکثر hard pause از زمان data packet trigger. حتی اگر exact echo نرسد، queue را آزاد می‌کند.
real-fin-upstream-nodestringهنگام فعال بودن اجباریhelper branch متمایزی که `FIN

setting مشترک stateful-flow-limit نیز اعمال می‌شود و پیش‌فرض 65536 دارد. هیچ setting‌ی برای TTL، ‏flags، ‏sequence، ‏acknowledgement یا checksum مربوط به FIN mirrored وجود ندارد؛ این مقدارها از trigger مشتق می‌شوند. node هم به next عادی در سطح اصلی و هم helper نیاز دارد. helper نام‌برده باید وجود داشته باشد، خود این IpManipulator نباشد، با next تفاوت داشته باشد و هنگام ساخت chain قابل دسترسی و binding باشد.

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"smuggle-fin": true,
"fin-sni-delay-ms": 20,
"fin-pause-timeout-ms": 1000,
"real-fin-upstream-node": "fin-helper"
},
"next": "normal-path"
}

عددهای بالا illustrative هستند. به‌ویژه hard timeout را براساس round trip موردانتظار helper، delay موردنظر و stall قابل قبول application انتخاب کنید و 1000 ms را مقدار universally suitable ندانید.

Trigger packet و FIN mirrored​

فرض کنید trigger packet چنین باشد:

client A:a -> server B:b
SEQ = S, ACK = K, payload length = L

IpManipulator ابتدا کل packet را به‌جای forward کردن روی branch عادی next retained می‌کند. سپس header‌های IPv4/TCP را copy، همه TCP payload را حذف، هر دو IP address و هر دو TCP port را swap و packet زیر را منتشر می‌کند:

server B:b -> client A:a
SEQ = K, ACK = S + L, flags = FIN|ACK, payload length = 0

sequence arithmetic همان حساب 32 بیتی TCP است. trigger نمی‌تواند SYN یا FIN داشته باشد، پس فقط طول payload آن acknowledgement را جلو می‌برد. header کپی‌شده فیلدهایی مثل IPv4 identification و TTL مربوط به trigger و TCP header length، options و window را حفظ می‌کند. total length مربوط به IPv4 به طول header-only کاهش می‌یابد، flags و فیلدهای sequence مطابق بالا جایگزین و checksum‌های معتبر IPv4/TCP برای egress درخواست می‌شوند. هیچ setting‌ی این فیلدها را جداگانه تغییر نمی‌دهد.

عددهای reverse طوری انتخاب شده‌اند که برای همان اتصال زنده باورپذیر باشند: K همان next server sequence است که client اعلام کرده انتظار دارد و S + L همه بایت‌های packet held سمت client را acknowledge می‌کند. خود client فقط data packet اصلی را فرستاده و این FIN را نفرستاده است. اگر helper path واقعاً reverse packet spoofشده را به client برساند و TCP stack آن را in-window بپذیرد، FIN می‌تواند جهت receive را half-close کند، expected server sequence را یک واحد جلو ببرد، ACK ایجاد کند یا اتصال را به شکل دیگری مختل کند. این اثر endpoint ممکن است رخ دهد، اما trick به آن نیاز ندارد.

packet crafted فوراً از real-fin-upstream-node فرستاده می‌شود و از stageهای TCP-bit در upstream، ‏stateful-SNI، ‏SNI Blender، ‏packet duplication یا port ghost این node عبور نمی‌کند. protocol swap و final checksum handling همچنان می‌توانند اعمال شوند. data packet اصلی برای مسیر عادی queue می‌ماند و هنگام release از stageهای بعدی تنظیم‌شده با ترتیب معمول آن‌ها عبور می‌کند.

تشخیص echo و timing مربوط به release​

برای release مبتنی بر echo، ‏topology مربوط به helper باید FIN ساخته‌شده را به عنوان packet downstream از همین IpManipulator برگرداند. پس از restore شدن protocol و port ghost در downstream، packet فقط در صورت برقراری همه شروط زیر match می‌شود:

  • یک packet کامل و fragmentنشده IPv4 TCP در جهت B:b -> A:a باشد
  • TCP payload آن صفر باشد
  • sequence دقیقاً K و acknowledgement دقیقاً S + L باشد
  • پس از نادیده گرفتن فقط ECE و CWR، ‏flags دقیقاً FIN|ACK باشند

IPv4 identification، ‏TTL، ‏TCP window و TCP options مقایسه نمی‌شوند. response عادی TCP به FIN معمولاً ACK با جهت و sequence semantics متفاوت است، پس echo موردنیاز نیست. هر flag اضافه‌ای به‌جز ECE یا CWR نیز match را رد می‌کند.

نخستین exact echo consume می‌شود و هرگز به‌سمت client forward نمی‌گردد. duplicate دقیق آن هنگام pending بودن release نیز consume می‌شود. در نخستین match، fin-sni-delay-ms، ‏release queue را schedule می‌کند؛ اما timer مربوط به fin-pause-timeout-ms که هنگام capture trigger شروع شده فعال می‌ماند. بنابراین sequence مؤثر چنین است:

t0: trigger is queued; hard timeout starts; mirrored FIN is sent
te: exact echoed FIN is consumed, if one arrives
release: earlier of (te + fin-sni-delay-ms) and
(t0 + fin-pause-timeout-ms)

در نتیجه اگر delay کامل اهمیت دارد، pause timeout باید از round trip موردانتظار helper به‌علاوه delay دلخواه بیشتر باشد. delay برابر 0 روی owner worker فوراً release می‌کند. اگر echo نرسد، hard timeout همچنان flow را آزاد می‌کند. failure در arm کردن initial hard timer، trigger queued را پیش از ارسال helper FIN فوراً آزاد می‌کند؛ failure در setup release میان worker‌ها پس از echo، timer سخت اولیه را به عنوان fallback نگه می‌دارد. callback‌های timer به generation دقیق pause متصل‌اند، پس callback قدیمی نمی‌تواند استفاده جدید همان tuple را release کند. پس از پایان release، packet بعدی که با FIN قدیمی match کند ترافیک downstream عادی است و دیگر توسط Smuggle-FIN consume نمی‌شود.

موارد queueشده و نحوه replay​

تا وقتی pause در orientation عادی packet chain فعال است، همه packet‌های parseable upstream روی forward tuple و همه packet‌های parseable downstream روی reverse tuple، صرف‌نظر از flags یا payload، نگه داشته می‌شوند. این شامل data retransmitted، ACK، ‏application data بعدی و FIN/RST واقعی است. trigger نخستین entry queue است؛ exact echo مربوط به FIN مصنوعی استثناست و به عنوان signal release consume می‌شود. packet‌های غیرقابل parse یا fragmented، queue را bypass می‌کنند و ممکن است از ترافیک held سبقت بگیرند. flow‌های نامرتبط هرگز پشت این flow نگه داشته نمی‌شوند.

queue حداکثر 256 packet نگه می‌دارد و byte limit جداگانه ندارد. اگر packet شماره 257 برسد یا queue نتواند رشد کند، batch موجود به اجبار و با ترتیب queue آزاد می‌شود، packet جاری عادی ادامه می‌یابد و flow confirmed علامت می‌خورد تا دوباره pause نشود. timeout و سایر forced-release pathها همان رفتار no-retry را دارند.

یک flow-owner worker queue را نگه می‌دارد. packet‌های reverse که روی worker دیگر می‌رسند پیش از queue شدن به owner worker کپی می‌شوند. ترتیب replay همان ترتیبی است که owner worker entryها را پذیرفته و در حالت چند worker لزوماً با یک ترتیب global روی wire برابر نیست. اگر cross-worker handoff ساخته یا submit نشود، آن reverse packet روی worker فعلی عبور می‌کند و pause فعال می‌ماند؛ در نتیجه ممکن است از queue سبقت بگیرد. روی هر worker در هر لحظه فقط یک flow می‌تواند توسط smuggle-fin pause شود؛ packet eligible برای flow دیگر روی همان worker عادی عبور می‌کند.

هر queue entry به خاطر می‌سپارد checksum آن به recalculation نیاز داشته یا نه. هنگام release، entryها یکی‌یکی و با ترتیب mixed-direction ذخیره‌شده replay می‌شوند:

  • packet‌های upstream از ابتدای pipeline مربوط به upstream در IpManipulator restart می‌شوند؛ record اکنون confirmed مربوط به Smuggle-FIN از reinjection جلوگیری می‌کند و trick‌های بعدی upstream و egress عادی یک بار اجرا می‌شوند
  • packet‌های downstream بلافاصله پس از smuggle-fin resume می‌شوند، چون restore شدن protocol و port ghost پیش از queue شدن انجام شده بود؛ stageهای بعدی downstream یک بار اجرا می‌شوند

Smuggle-FIN بایت‌های داخل TCP packet اصلی را split، ‏overlap، ‏rewrite یا reorder نمی‌کند. تغییر عمدی ordering آن temporal است: FIN مصنوعی reverse پیش از data اصلی held خارج می‌شود و ترافیک نامرتبط یا bypassشده ممکن است هنگام pause عبور کند. نگه داشتن هر دو جهت latency اضافه می‌کند و ممکن است retransmission timer، delayed-ACK، تغییر congestion/receive window یا stall قابل مشاهده application ایجاد کند.

آنچه هر participant ممکن است ببیند​

  • client: data اصلی خود را عادی می‌فرستد و queue شدن توسط WaterWall را مستقیم نمی‌بیند. اگر helper path ‏FIN spoofشده را به آن route کند، ممکن است آن را دریافت کند؛ پذیرش FIN می‌تواند اتصال را طبق توضیح بالا مختل یا close کند. با نزدیک شدن pause به RTO، ‏client ممکن است data held را retransmit کند.
  • Middlebox روی helper path: می‌تواند پیش از ظاهر شدن data روی branch عادی، FIN|ACK از server به client با sequence و acknowledgement مشتق‌شده از data فعلی client ببیند. دستگاهی که آن را برای flow state خود بپذیرد ممکن است یک جهت را closed علامت بزند، packet‌های بعدی را drop کند، parser state را عوض کند یا exact downstream echo مورد استفاده WaterWall را بسازد. دستگاه stateless یا دستگاهی که path، ‏window، ‏checksum یا flow history متفاوتی را validate می‌کند ممکن است آن را نادیده بگیرد.
  • server مقصد: معمولاً باید data اصلی client را بعداً از next دریافت کند؛ Smuggle-FIN خود آن را تغییر نداده است. packet مصنوعی reverse tuple دارد و از helper branch استفاده می‌کند، اما routing و طراحی branch در نهایت تعیین می‌کنند به endpoint واقعی یا observer سمت server برسد یا نه.

پس تکنیک به تفاوتی وابسته به مسیر نیاز دارد: دستگاه میانی باید FIN باورپذیر اولیه را معنادار بداند، در حالی که اتصال end-to-end واقعی آن‌قدر usable بماند که data اصلی delayed موفق شود. TCP normalization، ‏routing نامتقارن، ‏NAT state، SYN/FIN proxy، ‏sequence-window validation، ‏anti-spoofing، ‏checksum offload یا validation و رفتار endpoint همگی می‌توانند این فرض را باطل کنند.

طول عمر flow و رفتار هنگام failure​

پس از exact echo یا هر forced release، ‏four-tuple نرمال‌شده confirmed می‌ماند و injection بعدی Smuggle-FIN را bypass می‌کند. activity می‌تواند record confirmed را تا 20 دقیقه idle نگه دارد. SYN، ‏FIN یا RST در orientation یکسان record را صریحاً reset نمی‌کند؛ پس reuse سریع دقیقاً همان tuple ممکن است تا expiry همچنان trick را bypass کند. در chain‌های غیرمعمول که data packet eligible در جهت reverse از callback upstream می‌آید، record canonical واحد می‌تواند reorient شود؛ در آن حالت queue فعال قدیمی به‌جای replay دور انداخته می‌شود.

چون exact echo فقط با tuple، ‏flags، ‏payload length، ‏sequence و acknowledgement شناسایی می‌شود، یک FIN|ACK legitimate از server که اتفاقاً همه آن فیلدها را match کند قابل تمایز نیست و consume خواهد شد. برعکس، تغییر کوچک helper در sequence یا flags مانع confirmation می‌شود و flow را تا hard timeout نگه می‌دارد. هنگام shutdown node، packet‌های retained به‌جای forward شدن در teardown دور انداخته می‌شوند.

stateful-flow-limit جدول Smuggle-FIN را مستقل از سایر tableهای stateful محدود می‌کند. فشار admission به‌صورت fail-open است: candidate packet روی مسیر عادی می‌ماند و FIN mirrored ارسال نمی‌شود. تغییر flow affinity برای reverse packet‌ها با owner-worker handoff به‌شکل خاص تحمل می‌شود، اما qualificationهای ordering بالا را دارد.

تفاوت‌ها و interactionها​

برخلاف trick‌های TLS/SNI، ‏smuggle-fin نه ClientHello را پیدا می‌کند و نه تغییر می‌دهد. برخلاف smuggle-sni، یک FIN header-only تازه‌ساخته‌شده را از helper branch می‌فرستد و data اصلی را نگه می‌دارد؛ smuggle-sni، ‏ClientHello واقعی captureشده را از helper می‌فرستد. برخلاف synfin-sni، ‏TLS record، ‏fake SYN، close در همان جهت یا TCP sequence range هم‌پوشان نمی‌سازد. برخلاف packet duplication، helper packet آن tuple reverse، ‏flags و sequence متفاوت، payload صفر و release schedule جداگانه دارد.

وقتی rule‌های سازگاری خودشان اجازه دهند، می‌توان آن را همراه stateful SNI، ‏SNI Blender، ‏TCP-bit rewrite، ‏port ghost، ‏protocol swap یا packet duplication تنظیم کرد. این به معنی اجرای stageها روی FIN مصنوعی نیست: helper FIN از bypass path بالا استفاده می‌کند، اما packet‌های اصلی releaseشده وارد pipeline معمولی می‌شوند. timing اضافه pause و replay همچنان می‌تواند نحوه classification flow توسط trick‌های stateful بعدی را عوض کند. MTU shaping نهایی ممکن است data packet بزرگ releaseشده را segment کند، هرچند خود Smuggle-FIN آن را split نکرده است.

جدول‌های Stateful Flow​

وقتی یکی از first-sni، ‏smuggle-sni، ‏overlap-sni، ‏synfin-sni، ech-sni-trick یا smuggle-fin فعال باشد، خود IpManipulator مالکیت worker را برای جریان packet برقرار می‌کند. packetهای TCP/UDP بومی در upstream پیش از پردازش stateful به worker جریان می‌رسند. در downstream این انتخاب پس از بازیابی protocol و port و پیش از ادامهٔ stateful انجام می‌شود. این مرزهای داخلی dispatcher به node اضافی در تنظیمات یا انتخاب flow owner توسط RawSocket و TunDevice نیاز ندارند. trickهای stateless چنین الزامی اضافه نمی‌کنند. ProtoSwap ساده، هم encode و هم بازیابی protocol و checksum را روی worker فعلی انجام می‌دهد؛ خروجی fragment نیز مستقیم ارسال می‌شود و این تبدیل worker جریان را انتخاب یا fragmentها را نگه‌داری نمی‌کند. protocolهای opaque که هنوز tuple بومی TCP/UDP ندارند نیز روی worker فعلی ادامه می‌یابند.

packetهای retained به worker‌ای pin می‌شوند که capture، ‏delay barrier یا گروه held-packet را باز کرده است. بررسی‌های fail-open برای mismatch worker باقی می‌مانند؛ lineهای اتصال معمولی مالک اصلی خود را حفظ می‌کنند و dispatcher packet آن‌ها را جابه‌جا نمی‌کند. Smuggle-FIN نیز handoff جداگانهٔ ترافیک reverse به مالک flow دارای pause را با محدودیت‌های ordering و handoff بخش اختصاصی حفظ می‌کند.

گزینهنوعپیش‌فرضتوضیح
stateful-flow-limitinteger از 32 تا 104857665536سقف سخت flow‌های فعالی که هر trick فعال stateful می‌تواند دنبال کند.

هر یک از first-sni، ‏smuggle-sni، ‏overlap-sni، ‏synfin-sni، ech-sni-trick و smuggle-fin جدول محدود مستقل خود را با این limit دریافت می‌کند. جدول‌ها هرگز بیش از آن رشد نمی‌کنند. وقتی یک shard پر باشد، IpManipulator با بودجه ثابت و کوچک entryهایی را که idle deadline آن‌ها گذشته است reclaim می‌کند. اگر slot آزاد نشود، admission به‌صورت fail-open شکست می‌خورد، packet بدون تغییر ادامه می‌یابد و warning با rate limit ثبت می‌شود. برای پذیرفتن flow دیگر هیچ flow زنده‌ای evict نمی‌شود؛ در نتیجه record‌های ECH و Smuggle-FIN که مالک buffer‌های packet نگه‌داشته‌شده هستند محافظت می‌شوند.

هر جدول یک four-tuple نرمال‌شده را به‌عنوان key canonical استفاده می‌کند؛ بنابراین packet‌های forward و reverse، حتی میان worker‌ها، به همان record، ‏hash، ‏shard و lock می‌رسند. یک SYN جایگزین معتبر، پیش از initialize دوباره همان record در جهت جدید، timer‌ها، captureها و queue‌های generation قبلی را صریحاً cancel می‌کند. entry canonical تکراری پذیرفته نمی‌شود.

تعداد shardها از تعداد worker‌ها پیروی می‌کند و حداکثر 64 است. limit تنظیم‌شده طوری تقسیم می‌شود که مجموع limitهای shardها برابر مقدار درخواستی بماند. lookup از hash bucket و idle expiry از deadline heap مخصوص هر shard استفاده می‌کند تا در callback‌های packet نیازی به scan کل جدول نباشد. pointer مربوط به flow entry فقط تا زمانی معتبر است که mutex همان shard نگه داشته شده باشد؛ بنابراین timer‌ها و messageهای میان worker‌ها flow را با tuple نرمال‌شده و generation شناسایی می‌کنند.

هر جدول از یک seed هش مخصوص tunnel که توسط CSPRNG تولید می‌شود استفاده می‌کند تا انتخاب bucket از روی tuple‌های مشاهده‌شده قابل پیش‌بینی نباشد.

delay barrierهای First-SNI، ‏Smuggle-SNI و Overlap-SNI در هر flow حداکثر 16 packet و 256 KiB نگه می‌دارند. یک generation در سطح tunnel، action مربوط به release را به instance دقیق tuple متصل می‌کند تا timer قدیمی نتواند tuple استفاده‌شده مجدد را آزاد کند. batchها زیر shard lock جدا می‌شوند و بیرون آن forward می‌شوند. تمام‌شدن ظرفیت count یا byte با ترتیب FIFO fail-open می‌شود و reset، ‏timeout یا destruction جدول همه packet‌های نگه‌داشته‌شده را recycle می‌کند.

هر delayed release یک مسیر fail-open دارد: اگر timer آن قابل arm شدن نباشد، بایت‌های نگه‌داشته‌شده به‌جای باقی ماندن در state، بی‌درنگ و به‌ترتیب flush می‌شوند.

نگه‌داری‌های Overlap-SNI و SynFIN-SNI از timer‌های fail-open جداگانه برای هر flow استفاده می‌کنند که با tuple نرمال‌شده و generation غیرصفر نگه‌داری مشخص می‌شوند. timeout، ‏segment نگه‌داشته‌شده را بیرون از shard lock و بدون تغییر آزاد می‌کند. بسته‌شدن upstream نگه‌داری زنده هر دو trick را آزاد می‌کند و Overlap-SNI در بسته‌شدن downstream نیز همین کار را انجام می‌دهد. اگر idle expiry یا teardown جدول، record را پیش از callback مربوط به timer حذف کند، destructor آن record عمداً packet نگه‌داشته‌شده را دور می‌اندازد تا هنگام در اختیار داشتن shard lock یا در زمان shutdown چیزی forward نشود.

ناسازگاری‌ها​

parser هر جفت از مجموعه پنج‌عضوی stateful SNI یعنی first-sni، ‏smuggle-sni، overlap-sni، ‏synfin-sni و ech-sni-trick را رد می‌کند. همچنین هر یک از آن‌ها با sni-blender یا packet-duplicate در همان instance رد می‌شود. برای chain دو‌نودی پشتیبانی‌شده، بخش «قواعد ترکیب Stateful SNI» در بالا را ببینید.

ترتیب اجرای Runtime​

ترتیب upstream payload:

  1. smuggle-fin
  2. TCP flag rewrite
  3. synfin-sni
  4. ech-sni-trick
  5. overlap-sni
  6. smuggle-sni
  7. first-sni
  8. SNI Blender
  9. خروجی نهایی: port ghost، محاسبه checksum با پروتکل واقعی، protocol swap و سپس forward یا duplicate کردن packet

ترتیب downstream payload:

  1. بازیابی protocol
  2. بازیابی port ghost
  3. smuggle-fin
  4. ech-sni-trick
  5. overlap-sni
  6. TCP flag rewrite
  7. hook downstream مربوط به SNI Blender
  8. logging hook مربوط به smuggle-sni
  9. ارسال نهایی downstream

برای replay داخلی smuggle-fin، packet‌های upstream از ابتدای pipeline مربوط به upstream دوباره شروع می‌شوند، چون smuggle-fin نخستین stage است. packet‌های downstream بلافاصله پس از smuggle-fin ادامه می‌دهند؛ بازیابی protocol و port ghost پیش از قرارگرفتن آن‌ها در صف انجام شده است و تکرار نمی‌شود. این رفتار ترتیب عادی downstream بالا را تغییر نمی‌دهد.

Lifecycle و Packet Line​

IpManipulator یک packet tunnel خالص است:

  • metadata node، kNodeLayer3 را advertise می‌کند، در حالی که constraint هر دو همسایه kNodeLayerAnything باقی می‌ماند.
  • برای هر packet چرخه عمر اتصال جداگانه ندارد.
  • callback‌های payload مسیر اصلی runtime هستند.
  • Finish، Pause، Resume و Est برای استفاده عادی معتبر نیستند
  • نباید packet line را در runtime آزاد کند.

با Init مربوط به packet line در upstream، مسیر عادی next مقداردهی اولیه می‌شود. اگر branch‌های کمکی real-SNI یا real-FIN تنظیم شده باشند، آن‌ها نیز initialize می‌شوند. اگر branch‌های کمکی alias باشند، فقط یک بار Init دریافت می‌کنند.

محدودیت‌ها و Padding​

مقداراندازه
required_padding_left0 bytes
تعداد fragment‌های SNI Blender2 تا 16
protocol replacement number0 تا 255
stateful-flow-limit32 تا 1048576
طول hostname مربوط به ECH1 تا 255 bytes
capture مربوط به ECHحداکثر 16 packet و یک TLS record با اندازه 16384-byte

این node از chain فضای left padding نمی‌خواهد. بعضی trick‌ها برای ساخت packet‌های دست‌کاری‌شده، buffer را کپی یا buffer تازه‌ای تخصیص می‌دهند.

نکته‌ها​

  • پیاده‌سازی فعلی عمدتاً IPv4 را تغییر می‌دهد؛ این trick‌ها معمولاً IPv6 را نادیده می‌شود.
  • بسیاری از trick‌ها به packet کامل و fragmentنشده IPv4 TCP با header کامل TCP می‌خواهند.
  • بیشتر trick‌های SNI فقط upstream و فقط روی TLS ClientHello کار می‌کنند.
  • این node به این وابسته است که مصرف‌کنندهٔ خروجی درخواست checksum روی buffer همان packet را با sbufGetRecalculateChecksum() رعایت کند. کپی‌ها، packetهای نگه‌داری‌شده و هر خروجی segmentation درخواست مستقل دارند؛ پس از رشد فقط buffer بازگشتی معتبر است. پیام handoff شامل بایت خام، درخواست را همراه همان بایت‌ها نگه می‌دارد و روی buffer بازسازی‌شده قرار می‌دهد.
  • ترمیم incremental مربوط به یک تغییر، درخواست کامل قدیمی را حفظ می‌کند. ProtoSwap پیش از encoding ترمیم در پروتکل بومی را کامل و درخواست را پاک می‌کند؛ refusal یا no-match بایت‌ها و درخواست را تغییر نمی‌دهد. درخواست مصرف‌شده نباید از snapshot قدیمی دوباره تنظیم شود. خروجی دارای checksum عمداً خراب درخواست ندارد؛ نبود درخواست تضمین معتبر بودن checksum نیست.
  • بهتر است branch‌های کمکی از next معمول جدا باشند.
  • وقتی sni-blender فعال است، sni-blender-packets اجباری است و باید بین 2 و 16 باشد.
  • فیلد trick_sni_blender_packets_delay_max در struct زبان C وجود دارد، اما parser فعلی JSON آن را در دسترس قرار نمی‌دهد و از آن استفاده نمی‌کند.

متادیتای نود​

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

ویژگیمقدار
node flagskNodeFlagNone
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer3
layer_group_prev_nodekNodeLayerAnything
layer_group_next_nodekNodeLayerAnything
required_padding_left0 bytes