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

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"
}

تنظیمات​

فیلدهای سطح بالا:

فیلدنوعاجباریتوضیح
namestringبلهنام دلخواه node. باید داخل فایل config یکتا باشد.
typestringبلهباید دقیقاً "PacketsToStream" باشد.
settingsobjectخیرobject تنظیمات اختیاری.
nextstringبلهnode stream-oriented که data line مربوط به هر worker را دریافت می‌کند.

فیلدهای اختیاری settings:

گزینهنوعپیش‌فرضتوضیح
sensitive-modebooleanfalseفعال‌سازی heartbeat stream-side روی هر output line worker-local.
interval-msinteger50فاصله ارسال heartbeat بر حسب میلی‌ثانیه. حداقل 1. فقط وقتی sensitive-mode فعال است استفاده می‌شود.
tolerance-msinteger150حداکثر زمان انتظار برای heartbeat pong قبل از ساختن مجدد stream line. حداقل 1.
packet-validation-levelstring"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 sidepacket 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 packet Payload ساخته می‌شود
  • با upstream Init در سمت tunnel بعدی مقداردهی اولیه می‌شود
  • برای packet‌های بعدی روی همان worker reuse می‌شود
  • اگر بسته شود یا timeout حالت sensitive رخ دهد، دوباره ساخته می‌شود

خود packet line همان state مشترک worker است و با ساخته شدن دوباره line رو به stream از بین نمی‌رود.

Decode packet در مسیر برگشت​

ترافیک برگشتی از سمت stream در یک read stream جمع می‌شود. decoder سپس:

  1. منتظر می‌ماند تا به‌اندازهٔ حداقل header در IPv4 داده برسد
  2. بررسی می‌کند آیا ابتدای stream شبیه یک IPv4 packet معتبر است
  3. فیلد IPv4 total-length را می‌خواند
  4. منتظر می‌ماند تا به‌اندازهٔ total length بایت در buffer جمع شود
  5. دقیقاً همان تعداد بایت را به‌عنوان یک packet استخراج می‌کند
  6. در صورت فعال بودن این گزینه، packet decodeشده را اعتبارسنجی می‌کند
  7. 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 Estwarning log؛ انتظار نمی‌رود.
upstream Pause / Resumewarning log؛ از packet side انتظار نمی‌رود.
upstream Finishfatal guard؛ finish سمت packet انتظار نمی‌رود.
downstream Payloadبایت‌های stream را به packet‌های IPv4 decode می‌کند و به سمت قبلی می‌فرستد.
downstream EstEst را به packet side قبلی propagate می‌کند اگر متعلق به stream line فعال باشد.
downstream Pausepacket side را paused علامت می‌زند و downstream Pause را به packet side قبلی propagate می‌کند.
downstream Resumeحالت paused را پاک می‌کند و downstream Resume را به packet side قبلی propagate می‌کند.
downstream Finishstream-facing line فعال را destroy/recreate می‌کند؛ packet line زنده می‌ماند.
downstream Initfatal 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 flagskNodeFlagNone
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer3 | kNodeLayer4
layer_group_prev_nodekNodeLayer3
layer_group_next_nodekNodeLayer4
required_padding_left0 بایت