IpManipulator
IpManipulator یک packet tunnel برای دستکاری packetهای خام IPv4 در chainهای
لایه ۳ WaterWall است.
این نود برای مسیرهایی است که payload آنها یک packet خام IP است؛ مثلاً در کنار
TunDevice، RawSocket، WireGuardDevice یا نودهای packet دیگر. این نود را
نباید آن را در chain معمولی بایتهای stream یا TCP قرار داد.
بیشتر قابلیتهای این نود برای packet shaping و آزمایش رفتار middleboxها طراحی شدهاند. نتیجه کاملاً به مسیر شبکه بستگی دارد؛ ممکن است یک trick در یک شبکه کار کند، اما در مسیری دیگر باعث حذف packet یا رفتاری نامطلوب شود.
عملکرد کلی
- روی 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 میدهند.
این نود با 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هایی مثل real-sni-upstream-node،
crafted-server-hello-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"
}
فیلدهای لازم
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه و یکتای نود. |
type | string | باید دقیقاً "IpManipulator" باشد. |
settings | object | باید حداقل یک trick فعال داشته باشد. |
next | string | برای عبور عادی packet لازم است. بعضی trickها صریحاً به next در سطح اصلی نیاز دارند. |
اگر هیچ trickای فعال نباشد، ساخت نود شکست میخورد:
IpManipulator: no tricks are enabled, nothing to do
خلاصه trickها
| خانواده | تنظیمات اصلی | جهت | کاربرد |
|---|---|---|---|
| Protocol swap | protoswap-tcp, protoswap-udp | دو طرف | تغییر شماره پروتکل IPv4 و بازیابی آن در سمت مقابل. |
| SNI Blender | sni-blender, sni-blender-packets | upstream | قطعهقطعه کردن IPv4/TCP TLS ClientHello و فرستادن قطعهها با ترتیب درهم. |
| Packet duplicate | packet-duplicate | مسیر نهایی ارسال | ارسال چند کپی اضافه از packet نهایی و سپس نسخه اصلی. |
| first-sni | first-sni | upstream | فرستادن یک ClientHello جعلی پیش از نسخه اصلی. |
| smuggle-sni | smuggle-sni, real-sni-upstream-node | upstream | فرستادن ClientHello واقعی روی branch جدا و نسخه جعلی با تأخیر روی مسیر عادی. |
| overlap-sni | overlap-sni, crafted-server-hello-upstream-node | upstream با ردیابی downstream | همپوشانی ClientHello ساختهشده با بایتهای واقعی TLS. |
| ech-sni-trick | ech-sni-trick | upstream | فرستادن ClientHello جعلی جاسازیشده توسط TlsClient در payloadِ ECH، به شکل TCP segment خارج از ترتیب. |
| synfin-sni | synfin-sni | upstream | ترکیب نخستین بخش واقعی، packet بستن، SYN جعلی، ClientHello جعلی، filler و بقیه بایتهای واقعی. |
| smuggle-fin | smuggle-fin, real-fin-upstream-node | upstream با ردیابی echo در downstream | تزریق `FIN |
| TCP flag rewrite | up-tcp-bit-*, dw-tcp-bit-* | دو طرف | اجبار، toggle یا نگاشت دوباره flagهای TCP. |
| Port ghost | source-port-ghost, dest-port-ghost | اعمال در upstream، بازیابی در downstream | حمل port اصلی در انتهای packet و تغییر port فعال transport. |
Protocol Swap
در IPv4، فیلد Protocol مشخص میکند payload از چه نوعی است. برای مثال TCP عدد
6 و UDP عدد 17 دارد.
| گزینه | نوع | توضیح |
|---|---|---|
protoswap | integer | نام مستعار قدیمی protoswap-tcp. |
protoswap-tcp | integer | protocol number جایگزین برای packetهای TCP. |
protoswap-udp | integer | protocol number جایگزین برای packetهای UDP. |
بازه معتبر شماره پروتکل جایگزین از 0 تا 255 است.
اگر شماره پروتکل packet برگشتی با یکی از مقادیر جایگزین منطبق باشد، نود آن را به TCP یا UDP عادی برمیگرداند. برای تونل دوطرفه، سمت مقابل هم باید تنظیمات سازگار داشته باشد.
مقدار جایگزین نباید برابر شماره پروتکل واقعی TCP یعنی 6 یا UDP یعنی 17
باشد؛ حتی اگر فقط یکی از این دو خانواده تنظیم شده باشد. در غیر این صورت،
ترافیک واقعی با آن شماره از ترافیک mapشده قابل تشخیص نیست. اگر swap برای هر دو
خانواده TCP و UDP فعال باشد، مقدارهای جایگزین آنها نیز باید متفاوت باشند.
گزینه protoswap-tcp-2 حذف شده است و configuration قدیمی شامل آن رد میشود.
برای مهاجرت، تنها از mapping برگشتپذیر protoswap-tcp استفاده کنید. این تغییر
همچنین تضمین میکند همه fragmentهای یک datagram از IPv4 شماره پروتکل یکسانی
دریافت کنند.
trickهای upstream زمانی اجرا میشوند که packet هنوز شماره پروتکل واقعی خود را
دارد. درست پیش از خروج عادی، IpManipulator ابتدا port ghost را اعمال میکند،
checksumهای IPv4 و transport را با پروتکل واقعی کامل میکند، شماره پروتکل را
swap میکند و سپس checksumِ headerِ IPv4 را اصلاح میکند. در downstream، شماره
پروتکل پیش از بازیابی port ghost یا parse شدن packet توسط هر trick مربوط به
TCP/TLS بازیابی میشود.
اگر یک packet در upstream از قبل مقدار جایگزین تنظیمشده این نود را داشته باشد،
IpManipulator این نود را بخش صریح unwrap در یک جفت transport زنجیرهای در نظر
میگیرد: پروتکل واقعی را بازیابی میکند، checksum را با همان پروتکل کامل میکند
و packet را بدون mapping به جلو میفرستد. اگر باید packet پس از یک نود بعدی
همچنان wrapped بماند، همان protocol swap را روی آن نود فعال نکنید.
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-bitflags | boolean | false | پیش از بازنویسی، بایت اصلی flagهای TCP را ذخیره میکند تا جهت مقابل بتواند packet را بازیابی کند. |
این حالت زمانی مفید است که میخواهید packet در یک جهت با TCP flagهای تغییرکرده عبور کند و سپس در جهت برگشت به شکل اصلی TCP flags برگردد.
روند جهتها:
- در جهتی که actionهای
up-tcp-bit-*یاdw-tcp-bit-*دارد،IpManipulatorبایت کامل flagهای اصلی TCP را بهعنوان آخرین بایت packet IPv4 ذخیره میکند. - سپس TCP flagهای فعال packet را طبق actionهای همان جهت بازنویسی میکند.
- طول کل IPv4 را یک بایت زیاد میکند و packet را برای محاسبه دوباره checksum علامت میزند.
- در جهت مقابل، اگر آن جهت action بازنویسی TCP-bit نداشته باشد ولی جهت دیگر action داشته باشد، آخرین بایت packet خوانده میشود، TCP flags از آن بازیابی میشود، آن بایت حذف میشود، طول IPv4 یک بایت کم میشود و checksumها باید دوباره محاسبه شوند.
این actionهای تنظیمشده TCP-bit هستند که مشخص میکنند کدام جهت بایت پشتیبان را اضافه و کدام جهت آن را بازیابی کند. صرفاً فعالکردن این گزینه، نقش ثابتی به upstream یا downstream نمیدهد:
| actionهای تنظیمشده TCP-bit | رفتار upstream | رفتار downstream |
|---|---|---|
| فقط upstream | flagهای اصلی را پشتیبان میگیرد، یک بایت اضافه میکند و actionهای upstream را اعمال میکند. | flagها را از آخرین بایت بازیابی و آن بایت را حذف میکند. |
| فقط downstream | flagها را از آخرین بایت بازیابی و آن بایت را حذف میکند. | 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 کار میکند. همچنین فرض میکند آخرین بایت اضافهشده در مسیر بدون تغییر باقی میماند. اگر نود یا دستگاه شبکهای trailing payload را حذف، بازنویسی، padding یا تفسیر کند، بازیابی ممکن است شکست بخورد یا flag اشتباه برگردد.
هر عملیات بازنویسی همراه با preserve-tcp-bitflags یک بایت به packet اضافه
میکند. پیش از خروج نهایی، اگر این بایت، بایتهای port ghost یا هر دو باعث شوند
یک packet کامل IPv4/TCP از GLOBAL_MTU_SIZE بزرگتر شود، 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 خود را مستقیماً ارسال میکند.
اگر هر دو عملیات لازم هستند، از دو نود استفاده کنید:
... -> IpManipulator(stateful SNI only)
-> IpManipulator(sni-blender and/or packet-duplicate)
-> ...
در traversal مربوط به upstream، نود stateful باید ابتدا قرار گیرد. duplication
پیش از نود stateful، ورودیهای آن را duplicate میکند و معادل duplicate کردن
خروجی نهایی آن نیست. ترکیب sni-blender و packet-duplicate وقتی هیچ trick
stateful مربوط به SNI فعال نباشد همچنان پشتیبانی میشود.
ترکیبهای ردشده TCP-bit
در ترکیبهای زیر ساخت configuration شکست میخورد:
- هر 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 ClientHello را تغییر میدهد.
| گزینه | نوع | اجباری | توضیح |
|---|---|---|---|
sni-blender | boolean | بله | قطعهقطعه کردن TLS ClientHello و فرستادن قطعهها با ترتیب درهم را فعال میکند. |
sni-blender-packets | integer | وقتی فعال است بله | تعداد 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 به این نود ClientHello قابل تشخیص شود. - در سناریوهای محلی یا serverless، مسیریابی و capture سیستمعامل باید طوری باشد که packetهای مورد نظر از WaterWall عبور کنند.
این trick روی همه شبکهها کار نمیکند. بعضی مسیرها packetهای fragmentشده را دور میاندازند.
Packet Duplicate
| گزینه | نوع | توضیح |
|---|---|---|
packet-duplicate | integer بزرگتر از 0 | به این تعداد کپی اضافه از packet فرستاده میشود و سپس نسخه اصلی یک بار ارسال میشود. |
این مرحله در مسیر نهایی ارسال و پس از دیگر trickهای فعال اجرا میشود.
Port Ghost
Port ghost در مرحله نهایی ارسال upstream اعمال و در ابتدای downstream بازیابی میشود.
| گزینه | نوع | توضیح |
|---|---|---|
source-port-ghost | boolean | source port اصلی به انتهای packet افزوده میشود و source port فعال به یک high port قطعی تغییر میکند. |
dest-port-ghost | boolean | destination 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 کاملشده در
GLOBAL_MTU_SIZE جا میشود و سپس 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 و server-hello مربوط به overlap-SNI، tuple اصلی خود را حفظ میکنند و trailer مربوط به port ghost نمیگیرند.
first-sni
first-sni یک یا چند نسخه ساختهشده از ClientHello را پیش از نسخه اصلی میفرستد.
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
first-sni | string غیرخالی | اجباری برای فعالسازی | SNI نوشتهشده در نسخه ساختهشده. |
first-sni-count | integer بزرگتر از 0 | 1 | تعداد کپیهای جعلی پیش از نسخه اصلی. |
first-sni-replay-delay | ms، >= 0 | 0 | تأخیر میان تکرارهای جعلی پس از نخستین packet. |
first-sni-final-delay | ms، >= 0 | 0 | تأخیر میان آخرین نسخه جعلی و نسخه اصلی. |
first-sni-ttl | 0 تا 255 | حفظ TTL اصلی | TTL نسخه ساختهشده. |
first-sni-random-tcp-sequence | boolean | false | انتخاب تصادفی TCP sequence در نسخه ساختهشده. |
تنها نسخه ساختهشده تغییر میکند و نسخه اصلی در مسیر عادی باقی میماند. اگر ClientHello شامل TLS 1.3 PSK binder باشد و تغییر SNI نیاز به recompute binder داشته باشد، trick برای احتیاط اجرا نمیشود.
capture چندsegmentی فقط وقتی آغاز میشود که یک packet شامل segment نخست قابلتشخیص ClientHello باشد. payloadهای TCP نامرتبط و غیر TLS بدون ورود به صف prestart حدسی بلافاصله fail-open میشوند. ClientHelloهای شناختهشده و in-order همچنان میتوانند در چند segment از TCP قرار داشته باشند.
fragmentهای IPv4، شامل fragment نخست با MF=1 و fragmentهای بعدی با offset
غیرصفر، هرگز وارد capture مربوط به First-SNI نمیشوند و در مسیر عادی fail-open
ادامه میدهند. وقتی replay یا خروجی نهایی تأخیر دارد، tail مربوط به transcript
پیش از packetهای بعدی در یک FIFO محدود قرار میگیرد و همه در یک deadline مطلق
آزاد میشوند.
smuggle-sni
smuggle-sni یک TLS ClientHello record را در یک four-tuple و در یک یا چند TCP
segment، تا سقف 16 segment و 16 کیلوبایت، capture و reassemble میکند. ClientHello
واقعی را فوراً و با ترتیب درست از branch کمکی upstream میفرستد و پس از
smuggle-sni-delay-ms یک batch چندsegmentی از ClientHello جعلی ساختهشده را روی
مسیر عادی next ارسال میکند. batch جعلی مرزهای اصلی TCP segment، طول اصلی
payload هر segment، شمارههای اصلی TCP sequence و هر payload پس از انتهای
ClientHello record را دقیقاً حفظ میکند. در صورت تفاوت طول، timeout یا ناپیوستگی
fragmentها، یا شکست generation، smuggle-sni فوراً به مسیر عادی fail-open
میشود.
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
smuggle-sni | string غیرخالی | اجباری برای فعالسازی | SNI داخل نسخه ساختهشده. |
smuggle-sni-delay-ms | ms، >= 0 | 0 | تأخیر ارسال batch جعلی روی مسیر معمولی. |
real-sni-upstream-node | string | اجباری | branchی که ClientHello واقعی را بیدرنگ میگیرد. |
این trick به next معمول در سطح اصلی نیاز دارد. نود کمکی باید وجود داشته باشد
و نباید به خود IpManipulator اشاره کند. در عمل بهتر است این branch کمکی جدا
از next اصلی باشد.
IpManipulator برای ساخت ClientHello دستکاریشده یک TlsClient داخلی no-chain
میسازد.
packetهای بعدی در پنجره تأخیر وارد همان FIFO محدود میشوند و deadline مطلق flow
را به کار میبرند. transcript جعلی پیش از این FIFO schedule میشود؛ FIN و
RST پشت دادههایی که از قبل در صف هستند باقی میمانند.
overlap-sni
overlap-sni packetهای اولیه TLS را capture میکند، یک ClientHello شبیه Chrome
میسازد، یک packet دستکاریشده TLS در سمت سرور را از branch کمکی میفرستد و
بایتهای ساختهشده و واقعی را در محدوده TCP sequence با هم همپوشان میکند.
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
overlap-sni | string غیرخالی | اجباری برای فعالسازی | SNI برای ClientHello generated. |
overlap-sni-delay-ms | ms، >= 0 | 0 | تاخیر packetهای بعد از fake SYN و packetهای بعدی در overlap window. |
overlap-sni-syn-ttl | 0 تا 255 | حفظ TTL اصلی | TTL برای fake TCP SYN. |
crafted-server-hello-upstream-node | string | اجباری | helper branch برای crafted server-side TLS packet. |
این trick به next معمول در سطح اصلی نیاز دارد. Branch کمکی باید وجود داشته
باشد و نباید به خود IpManipulator اشاره کند.
packetهای بعدی upstream از deadline مطلق overlap window و یک FIFO محدود استفاده
میکنند و برای هر packet یک نسخه تازه از overlap-sni-delay-ms دریافت نمیکنند.
tail ساختهشده overlap پیش از آنکه FIFO بتواند آزاد شود schedule میشود.
ech-sni-trick
ech-sni-trick برای هماهنگی با TlsClient.settings.ech-sni-trick طراحی شده
است. TlsClient یک fake ClientHello را داخل GREASE
encrypted_client_hello payload قرار میدهد و IpManipulator همان byte range را
به شکل TCP segment خارج از ترتیب میفرستد، بدون آنکه بایتهای ClientHello اصلی
را تغییر دهد.
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
ech-sni-trick | string با طول 1 تا 255 بایت | اجباری برای فعالسازی | باید با مقدار مرتبط در TlsClient.settings.ech-sni-trick هماهنگ باشد. |
data-shard-1-delay | ms، >= 0 | 0 | تاخیر آزاد کردن اولین original packet بعد از ارسال fake inner ClientHello segment. |
data-shard-2-delay | ms، >= 0 | 0 | تأخیر اضافه پیش از آزاد کردن original packetهای 2 تا N بهصورت متوالی و با ترتیب اصلی sequence. در capture تکpacketی نادیده گرفته میشود. |
مجموع تأخیرها باید در بازه unsigned 32-bit پشتیبانیشده جا شود. Packet خارج از ترتیب
با flagِ PSH فرستاده میشود. اگر packet ساختهشده از GLOBAL_MTU_SIZE بزرگتر باشد،
flow بهجای تغییر شکل رد میشود.
capture براساس sequence است و به شماره ترتیبی packetها وابسته نیست. یک TCP SYN
بدون payload، چه با flagهای ECN یعنی ECE/CWR و چه بدون آنها، flow را باز
میکند. SYN دارای داده TCP Fast Open پشتیبانی نمیشود و بدون تغییر عبور میکند.
capture فقط وقتی آغاز میشود که payload مربوط به upstream شامل prefix معتبر،
کامل یا ناقص، از TLS ClientHello باشد؛ payloadهای نامرتبط و غیر TLS هرگز در صف
prestart حدسی قرار نمیگیرند. byteهای پیوسته از header پنجبایتی TLS record و
header چهاربایتی handshake اعتبارسنجی میشوند تا طول دقیق record کامل شود.
packetهای بدون payload، از جمله ACK-only پیش از capture یا هنگام آن، بدون تغییر
عبور میکنند و capture را جلو نمیبرند یا غیرفعال نمیکنند.
capture به 16 packet، یک TLS record با اندازه 16384 بایت و timeout برابر 1500 ms محدود است. gap در sequence، record malformed یا پشتیبانینشده، عبور از محدودیت packet یا اندازه، timeout، یا overlapی که نتوان دقیقاً retransmission بودن آن را اثبات کرد باعث fail-open میشود: originalهای نگهداشتهشده بدون تغییر و با ترتیب TCP sequence آزاد میشوند و generation همان flow وارد حالت passthrough میشود. هنگام capture ناقص، retransmission دقیقی که sequence، طول و byteهایش با یک segment نگهداشتهشده برابر باشد consume میشود و capture را غیرفعال نمیکند؛ overlap جزئی یا مبهم fail-open میشود.
پس از capture، IpManipulator به یک outer SNI معتبر، یک extension معتبر
encrypted_client_hello و دقیقاً یک TLS ClientHello داخلی جاسازیشده نیاز دارد
که SNI آن کامل parse شود و byteبهbyte با ech-sni-trick برابر باشد. candidate
گمشده، malformed، ناقص، ناسازگار یا مبهم بدون ارسال packet جعلی داخلی fail-open
میشود. این مکانیزم camouflage از نوع GREASE است، نه ECH رمزنگاریشده واقعی.
در حالت موفق، byteهای ClientHello اصلی، مرزهای packet و شمارههای TCP sequence
بدون تغییر میمانند. IpManipulator ابتدا یک packet خارج از ترتیب TCP را که فقط
شامل byteهای ClientHello داخلی منطبق است ارسال میکند. سپس:
- original packet شماره 1 را پس از
data-shard-1-delayآزاد میکند؛ - بهاندازه
data-shard-2-delayاضافه منتظر میماند؛ و - original packetهای 2 تا N را بهصورت متوالی و با ترتیب اصلی آزاد میکند.
capture تکpacketی فقط از تأخیر نخست استفاده میکند. capture دوpacketی timing دو shard اصلی را حفظ میکند، اما capture بزرگتر چند timer message با deadline برابر ایجاد نمیکند که بتوانند packetها را reorder کنند.
هنگام آزادسازی با تأخیر، فقط retransmission دقیق یک original segment که هنوز
pending است swallow میشود. ترافیک ACK-only، payload بعدی بدون overlap و
overlapهای جزئی یا مبهم بدون تغییر عبور میکنند. TCP FIN و RST همچنان ترافیک
lifecycle اتصال هستند و هرگز swallow نمیشوند. close منطبق در هر جهت همه original
packetهایی را که هنوز منتظر آزادسازی هستند cancel میکند؛ close در upstream
هنگام capture ناقص ابتدا originalهای نگهداشتهشده را آزاد میکند، اما close در
downstream همان capture ناقص را بدون injection بعدی دور میاندازد.
هر original با تأخیر هم به generation غیرصفر flow مربوط به ECH که آن را capture کرده و هم به four-tuple آن متصل است. استفاده دوباره از همان four-tuple یک generation جدید آغاز میکند و ابتدا capture ناقص و originalهای pending مربوط به generation قدیمی را invalid میکند؛ بنابراین timer قدیمی capture یا release نمیتواند داده ClientHello قدیمی را inject کند. تا زمانی که originalها pending هستند، flow از idle expiry عادی محافظت میشود و پس از آخرین release، idle expiry عادی از سر گرفته میشود.
synfin-sni
synfin-sni یک trick پیشرفته در upstream برای TLS ClientHello است. ابتدا بخشی
واقعی از داده TLS و سپس packet بستن، SYN جعلی، ClientHello جعلی ساختهشده، filler
شبیه TLS و در پایان بقیه بایتهای واقعی ClientHello را میفرستد.
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
synfin-sni | string غیرخالی | اجباری برای فعالسازی | SNI مربوط به ClientHello ساختهشده. |
synfin-sni-additional-range-min | 0 تا 65535 | 0 | کمترین تعداد بایت اضافه از ClientHello واقعی در chunk نخست. |
synfin-sni-additional-range-max | 0 تا 65535 | اگر فقط min باشد همان min، وگرنه 0 | بیشترین تعداد بایت اضافه؛ برای هر flow مقداری تصادفی انتخاب و در بازه محدود میشود. |
synfin-sni-syn-ttl | 0 تا 255 | حفظ TTL اصلی | TTL مربوط به SYN جعلی. |
synfin-sni-fin-ttl | 0 تا 255 | حفظ TTL اصلی | TTL مربوط به packet بستن. |
synfin-sni-fake-ttl | 0 تا 255 | حفظ TTL اصلی | TTL مربوط به packet کامل و جعلی ClientHello. |
synfin-sni-random-syn-checksum | boolean | false | checksumهای IPv4/TCP مربوط به SYN جعلی را تصادفی میکند. |
synfin-sni-random-fin-checksum | boolean | false | checksumهای packet بستن را تصادفی میکند. |
synfin-sni-random-syn-sequence | boolean | false | sequence مربوط به SYN جعلی را تصادفی میکند. |
synfin-sni-random-fin-sequence | boolean | false | sequence مربوط به packet بستن را تصادفی میکند. |
synfin-sni-use-rst | boolean | false | در packet بستن بهجای `FIN |
TTL برابر 0 واقعاً مقدار آن را صفر میکند. اگر میخواهید TTL اصلی حفظ شود، کلید
TTL را اصلاً قرار ندهید.
sequence ساختهشده بهصورت synchronous و با ترتیب مستندشده ارسال میشود و هیچ sleep مصنوعی 20 ms برای هر packet یا تأخیر pacing قابل تنظیم ندارد.
smuggle-fin
smuggle-fin یک FIN|ACK آینهشده روی branch کمکی تزریق میکند و ترافیک را روی
worker مالک flow تا دریافت echo مورد انتظار در downstream و پایان تأخیر اختیاری
در صف نگه میدارد. packetهای reverse منطبق که روی worker دیگری میرسند پیش از
ورود به همان صف مرتب به worker مالک کپی میشوند. هر packet صفشده intent مستقل
خود را برای محاسبه دوباره checksum حفظ میکند.
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
smuggle-fin | boolean | اجباری برای فعالسازی | تزریق FIN/ACK آینهشده را فعال میکند. |
fin-sni-delay-ms | ms، >= 0 | 0 | تأخیر پس از echo در downstream و پیش از پخش دوباره packetهای صفشده. |
real-fin-upstream-node | string | اجباری | branch کمکی برای FIN/ACK آینهشده و دستکاریشده. |
این trick به next معمول در سطح اصلی نیاز دارد. نود کمکی باید وجود داشته باشد
و نباید به خود IpManipulator اشاره کند.
packetهای بدون TCP payload، packetهایی که از قبل SYN، FIN یا RST هستند،
packetهای غیر TCP و packetهای fragmentشده IPv4 بدون تغییر عبور میکنند.
جدولهای Stateful Flow
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
stateful-flow-limit | integer از 32 تا 1048576 | 65536 | سقف سخت 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 شناسایی میکنند.
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 میکند.
ناسازگاریها
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:
smuggle-fin- TCP flag rewrite
synfin-sniech-sni-trickoverlap-snismuggle-snifirst-sni- SNI Blender
- خروجی نهایی: port ghost، محاسبه checksum با پروتکل واقعی، protocol swap و سپس forward یا duplicate کردن packet
ترتیب downstream payload:
- بازیابی protocol
- بازیابی port ghost
smuggle-finech-sni-trickoverlap-sni- TCP flag rewrite
- hook downstream مربوط به SNI Blender
- logging hook مربوط به
smuggle-sni - ارسال نهایی 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 نود،
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 یا server hello ساختهشده توسط
overlap-SNI تنظیم شده باشند، آنها نیز مقداردهی میشوند. اگر چند نقش کمکی به یک
branch اشاره کنند، آن branch فقط یک بار Init دریافت میکند.
محدودیتها و Padding
| مقدار | اندازه |
|---|---|
required_padding_left | 0 bytes |
| تعداد fragmentهای SNI Blender | 2 تا 16 |
| protocol replacement number | 0 تا 255 |
stateful-flow-limit | 32 تا 1048576 |
| طول hostname مربوط به ECH | 1 تا 255 bytes |
| capture مربوط به ECH | حداکثر 16 packet و یک TLS record با اندازه 16384-byte |
این نود از chain فضای left padding نمیخواهد. بعضی trickها برای ساخت packetهای دستکاریشده، بافر را کپی یا بافر تازهای تخصیص میدهند.
نکتهها
- پیادهسازی فعلی عمدتاً IPv4 را تغییر میدهد؛ این trickها معمولاً IPv6 را نادیده میشود.
- بسیاری از trickها به packet کامل و fragmentنشده IPv4 TCP با header کامل TCP میخواهند.
- بیشتر trickهای SNI فقط upstream و فقط روی TLS ClientHello کار میکنند.
- این نود به این وابسته است که output packet بعدی
line->recalculate_checksumرا رعایت کند. - بهتر است branchهای کمکی از
nextمعمول جدا باشند. - وقتی
sni-blenderفعال است،sni-blender-packetsاجباری است و باید بین2و16باشد. - فیلد
trick_sni_blender_packets_delay_maxدر struct زبان C وجود دارد، اما parser فعلی JSON آن را در دسترس قرار نمیدهد و از آن استفاده نمیکند.