PacketsToStream
PacketsToStream بخش packet-oriented یک chain را به بخش stream-oriented آن متصل میکند. این node payloadهای packet را از سمت قبلی میگیرد، برای هر worker یک line رو به stream در سمت node بعدی میسازد و بایتهای خام هر packet IPv4 را روی همان line مینویسد.
پیادهسازی فعلی فقط از IPv4 پشتیبانی میکند و headerless است:
IPv4 packet bytes + IPv4 packet bytes + IPv4 packet bytes + ...
هیچ length prefix دوبایتی وجود ندارد. مرز packetها با فیلد total-length در header هر packet IPv4 بازیابی میشود. در مستندات قدیمی ممکن است نام PacketAsData را ببینید، اما نام نوع فعلی node PacketsToStream است.
کار این node
- payloadهای packet را از سمت قبلی دریافت میکند.
- در صورت نیاز، برای هر worker یک line معمول WaterWall رو به stream میسازد.
- همان line را برای packetهای بعدی روی آن worker دوباره به کار میگیرد.
- اگر buffer همان packet درخواست کرده باشد، پیش از تبدیل packet به بایتهای stream checksum مربوط به IPv4 را دوباره محاسبه میکند.
- packetهای IPv4 ناسازگار یا نامعتبر را کنار میگذارد.
- packetهای معتبر IPv4 را بدون افزودن framing header روی stream مینویسد.
- بایتهای برگشتی stream را در buffer نگه میدارد و با استفاده از فیلد
total-lengthمرز packetهای IPv4 را بازیابی میکند. - packetهای بازیابیشده را روی packet line همان worker مالک stream دریافتی به سمت قبلی برمیگرداند.
- ترافیک برگشتی هر inner flow را روی هر یک از stream lineهای خود میپذیرد.
- اگر line رو به stream بسته شود یا heartbeat در sensitive mode timeout شود، line تازهای میسازد.
این یک adapter میان packet و stream بر پایه parsing مربوط به IPv4 است، نه یک protocol عمومی برای framing بایتها.
جایگاه رایج
packet input به stream transport:
TunDevice -> PacketsToStream -> TcpConnector
جفت با StreamToPackets روی سرور دیگر:
server1: TunDevice -> PacketsToStream -> TcpConnector
server2: TcpListener -> StreamToPackets -> TunDevice
این چیدمان کار میکند، چون PacketsToStream و StreamToPackets هر دو از قالب یکسانِ packetهای خام IPv4 پشتسرهم استفاده میکنند.
نمونه تنظیم
{
"name": "packet-to-stream",
"type": "PacketsToStream",
"settings": {
"sensitive-mode": true,
"interval-ms": 50,
"tolerance-ms": 150,
"packet-validation-level": "hard"
},
"next": "stream-node"
}
تنظیمات
فیلدهای سطح بالا:
| فیلد | نوع | اجباری | توضیح |
|---|---|---|---|
name | string | بله | نام دلخواه node. باید داخل فایل config یکتا باشد. |
type | string | بله | باید دقیقاً "PacketsToStream" باشد. |
settings | object | خیر | object تنظیمات اختیاری. |
next | string | بله | node stream-oriented که data line مربوط به هر worker را دریافت میکند. |
فیلدهای اختیاری settings:
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
sensitive-mode | boolean | false | فعالسازی heartbeat stream-side روی هر output line worker-local. |
interval-ms | integer | 50 | فاصله ارسال heartbeat بر حسب میلیثانیه. حداقل 1. فقط وقتی sensitive-mode فعال است استفاده میشود. |
tolerance-ms | integer | 150 | حداکثر زمان انتظار برای heartbeat pong قبل از ساختن مجدد stream line. حداقل 1. |
packet-validation-level | string | "none" | حالت اعتبارسنجی برای packetهای decode شده از data برگشتی stream. مقادیر: "none"، "loose"، "hard". |
هیچ setting اختصاصی و اجباری برای این tunnel وجود ندارد.
فرمت wire
قالب wire در سمت stream از packetهای کامل IPv4 تشکیل شده که بدون فاصله پشت سر هم قرار میگیرند:
packet A: N bytes, where IPv4 total-length = N
packet B: M bytes, where IPv4 total-length = M
packet C: K bytes, where IPv4 total-length = K
PacketsToStream هیچ length field، message type یا metadata به ابتدای داده اضافه نمیکند. peer باید با خواندن header مربوط به IPv4 مرز packetها را پیدا کند.
packetهایی که روی stream فرستاده میشوند باید شرایط زیر را داشته باشند:
- دستکم به اندازه حداقل header مربوط به IPv4 طول داشته باشند
- از
kMaxAllowedPacketLengthبزرگتر نباشند - IP version برابر
4باشد - IPv4 total length برابر طول buffer باشد
IPv6، payloadهای غیر IPv4، packetهای نامعتبر IPv4 و packetهایی که پس از total-length بایت اضافه دارند کنار گذاشته میشوند؛ زیرا parser سمت peer را از sync خارج میکنند.
مدل جریان
خلاصه جهت:
| جهت | رفتار |
|---|---|
| packet side به stream side | packet IPv4 معتبر بهصورت خام روی stream line worker-local ارسال میشود. |
| stream side به packet side | بایتهای stream در buffer جمع و به packetهای IPv4 decode میشوند؛ سپس پس از اعتبارسنجی اختیاری، در جهت downstream روی packet line همان worker مالک stream دریافتی میروند. |
stream line worker-local
PacketsToStream state خود را روی packet line مشترک worker نگه میدارد. برای هر worker، هنگام رسیدن نخستین ترافیک یک line معمولی رو به stream در سمت tunnel بعدی میسازد.
این line رو به stream:
- روی upstream packet
Initیا اولین upstream packetPayloadساخته میشود - با upstream
Initدر سمت tunnel بعدی مقداردهی اولیه میشود - برای packetهای بعدی روی همان worker reuse میشود
- اگر بسته شود یا timeout حالت sensitive رخ دهد، دوباره ساخته میشود
خود packet line همان state مشترک worker است و با ساخته شدن دوباره line رو به stream از بین نمیرود.
Decode packet در مسیر برگشت
ترافیک برگشتی از سمت stream در یک read stream جمع میشود. decoder سپس:
- منتظر میماند تا بهاندازهٔ حداقل header در IPv4 داده برسد
- بررسی میکند آیا ابتدای stream شبیه یک IPv4 packet معتبر است
- فیلد IPv4 total-length را میخواند
- منتظر میماند تا بهاندازهٔ total length بایت در buffer جمع شود
- دقیقاً همان تعداد بایت را بهعنوان یک packet استخراج میکند
- در صورت فعال بودن این گزینه، packet decodeشده را اعتبارسنجی میکند
- packet را روی packet line همان worker مالک stream دریافتی به node سمت packet میفرستد
هر flow داخلی ممکن است روی هر stream line برسد. decoder روی همان worker ادامه میدهد و مصرفکنندهٔ stateful بعدی خودش worker جریان را انتخاب میکند. parser، state مربوط به heartbeat در sensitive mode و ساخت دوباره output line نیز روی worker مالک stream باقی میمانند. مسیر ارسال برای هر worker یک stream line دارد.
parser در برابر داده نامعتبر مقاوم است. اگر ابتدای stream شروع معتبر یک packet IPv4 نباشد، decoder در یک پنجره محدود به دنبال نقطه resync میگردد و بایتها را تا header معتبر بعدی کنار میگذارد. parser همواره جلو میرود و از محدوده buffer خارج نمیشود.
حد overflow فعلی read stream:
65536 * 2 bytes
اگر buffer از این حد بزرگتر شود، read stream خالی میشود.
اعتبارسنجی packet
packet-validation-level روی packetهایی اعمال میشود که از data stream downstream decode شدهاند، قبل از اینکه به packet side ارسال شوند.
این گزینه جای بررسیهای همیشگیِ قابلارسال بودن packet سمت ورودی را نمیگیرد. ورودی سمت packet همچنان باید یک IPv4 سازگار باشد تا peer بتواند آن را بخواند.
| سطح | رفتار |
|---|---|
"none" | پیشفرض. هیچ اعتبارسنجی configurable بعد از استخراج ندارد. extractor هنوز بررسیهای structural سبک انجام میدهد تا بتواند مرز packetها را پیدا کند. |
"loose" | packetهای غیر IPv4 و packetهای نامعتبر IPv4 را کنار میگذارد. حداقل اندازه header، version، طول header مربوط به IPv4 و برابری total length با طول packet را بررسی میکند. |
"hard" | loose را اعمال میکند، IPv4 header checksum را verify میکند، و checksumهای TCP، UDP و ICMP را برای packetهای non-fragmented verify میکند. IPv4 UDP checksum برابر 0 پذیرفته میشود. |
برای packetهای fragmentشده IPv4 در حالت "hard"، فقط checksum مربوط به header در IPv4 بررسی میشود. بررسی checksum در لایه transport به reassembly نیاز دارد و انجام نمیشود.
وقتی یک packet decodeشده در اعتبارسنجی رد شود، tunnel سطح اعتبارسنجی و دلیل را بهصورت warning در log مینویسد.
Sensitive mode
Sensitive mode سلامت stream line را بررسی میکند. چون قالب wire، control frame جداگانهای ندارد، این قابلیت با packetهای heartbeat معتبر IPv4 پیادهسازی شده است.
وقتی فعال است:
- هر worker یک heartbeat timer میگیرد
- هر
interval-ms،PacketsToStreamیک heartbeat ping روی stream-facing line فعال میفرستد - ping یک packet IPv4 حداقلی با protocol
0xFDو payload پنجبایتی پر از0xFFاست StreamToPacketsسمت peer آن packet را بهعنوان ping در نظر میگیرد و یک heartbeat pong متناظر برمیگرداند- pong یک packet IPv4 حداقلی با protocol
0xFDو payload پنجبایتی پر از0xDDاست - ping و pong به packet side ارسال نمیشوند
- اگر تا پایان
tolerance-msهیچ pong نرسد، stream-facing line فعلی بهصورت local بسته و سپس دوباره ساخته میشود
در هر لحظه برای هر worker فقط یک ping منتظر پاسخ است. رسیدن pong پس از زمان tolerance نیز باعث reset شدن line میشود.
Pause، Resume و Finish
این node مالک stream-facing line است و آن را به یک worker packet line جدا نگاشت میکند. کنترل جریان endpoint باید حفظ شود و bypass دائمی ممکن نیست؛ parser مربوط به IPv4، نگاشت هویت line و ساخت دوباره line همچنان لازماند. تکرار Pause یا Resume بیاثر است؛ چند Pause به چند Resume نیاز ندارد.
رفتار callback:
callback دریافتشده توسط PacketsToStream | رفتار |
|---|---|
upstream Init | اطمینان از وجود stream-facing line worker-local. |
upstream Payload | در صورت درخواست checksum را محاسبه میکند، IPv4 forwardability را بررسی میکند، packet خام را به stream line میفرستد. |
upstream Est | warning log؛ انتظار نمیرود. |
upstream Pause / Resume | warning log؛ از packet side انتظار نمیرود. |
upstream Finish | fatal guard؛ finish سمت packet انتظار نمیرود. |
downstream Payload | بایتهای stream را به packetهای IPv4 decode میکند و به سمت قبلی میفرستد. |
downstream Est | Est را به packet side قبلی propagate میکند اگر متعلق به stream line فعال باشد. |
downstream Pause | packet side را paused علامت میزند و downstream Pause را به packet side قبلی propagate میکند. |
downstream Resume | حالت paused را پاک میکند و downstream Resume را به packet side قبلی propagate میکند. |
downstream Finish | stream-facing line فعال را destroy/recreate میکند؛ packet line زنده میماند. |
downstream Init | fatal guard؛ path callback معتبری نیست. |
اگر stream line در حالت pause باشد و node قبلی سمت packet به backpressure توجه نکند، payload ورودی بهجای صف شدن کنار گذاشته میشود.
پس از ساخت یک stream line زنده و unpaused، این node به source سمت packet یک Resume میفرستد تا Pause باقیمانده از stream قبلی آزاد شود؛ نیازی به packet تازه یا Est نیست. Pause هنگام Init خط تازه، source را paused نگه میدارد. packet یا heartbeat که باعث ساخت line شده، پیش از این Resume تعیین تکلیف میشود تا ورودی تازه از آن سبقت نگیرد. Init ناموفق و shutdown باعث ارسال Resume بازیابی نمیشوند.
heartbeat Ping در sensitive mode هنگام Pause هم ارسال میشود. Pause مهلت دریافت Pong را متوقف نمیکند و ارسال Ping نیز مسیر packet عادی را دوباره باز نمیکند.
محاسبه مجدد checksum
PacketsToStream در ابتدای callbackِ packet، پیش از هر drop یا callback دیگر،
درخواست sbufGetRecalculateChecksum(buf) را از همان buffer میگیرد و پاک میکند.
drop یا اجرای بازگشتی Init درخواست buffer دیگری را تغییر نمیدهد. در صورت
درخواست، ترمیم checksum پیش از تبدیل packet به بایتهای stream کامل میشود؛
flag محلی buffer روی wire ارسال نمیشود. مقدار false تضمین اعتبار checksum نیست.
packet خروجی باید با قواعد پایهٔ framing در decoder سازگار باشد: نسخهٔ IPv4، header حداقل ۲۰ بایت که در طول کل اعلامشده جا شود، و طول کل برابر با اندازهٔ buffer و در محدودهٔ kMaxAllowedPacketLength. این بررسیها مستقل از packet-validation-level هستند؛ IPv6 و framing نامعتبر drop میشوند.
این رفتار برای nodeهای دستکاری packet مهم است که عمداً packet را برای ترمیم checksum علامتگذاری میکنند.
tunnel هیچ بایتی به ابتدای payload اضافه نمیکند؛ بنابراین به left padding اضافی نیاز ندارد.
نکتههای عملی
- آن را با
StreamToPacketsدر سمت دیگر جفت کنید. - با framing قدیمی 2-byte-prefix جفت نکنید مگر اینکه peer به فرمت فعلی IPv4-total-length بهروز شده باشد.
- وقتی میخواهید lwIP flowهای TCP/UDP را بهعنوان lineهای عادی WaterWall بازسازی کند، از
PacketsToConnectionاستفاده کنید نه از این node. - برای تشخیص دقیقتر خرابی داده در stream برگشتی،
packet-validation-level: "hard"را انتخاب کنید؛ در این حالت هزینهٔ محاسبهٔ checksum را هم در نظر بگیرید. - parser میتواند پس از داده نامعتبر دوباره sync شود، اما یک stream مخرب ممکن است دادههایی تولید کند که تا زمان realignment از نظر ساختاری packet معتبر به نظر برسند.
متادیتای node
متادیتای برگرفته از کد منبع:
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagNone |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer3 | kNodeLayer4 |
layer_group_prev_node | kNodeLayer3 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 0 بایت |