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

TunDevice

TunDevice یک packet chain در WaterWall را به TUN interface سیستم‌عامل وصل می‌کند. packet‌های لایه ۳ را از device مجازی می‌خواند و وارد chain می‌کند؛ packet‌هایی را هم که از chain برمی‌گردند در همان TUN device می‌نویسد.

این node adapter لایه ۳ است، نه یک tunnel اتصال‌محور. برای هر connection line جداگانه‌ای نمی‌سازد؛ packet‌های runtime از packet line‌هایی استفاده می‌کنند که chain برای هر worker ساخته است.

در callback دریافت TunDevice فقط IPv4 برای chain پذیرفته می‌شود. با فعال بودن GSO در Linux، خارج از حالت جفت مستقیم توضیح‌داده‌شده در ادامه، reader checksum‌های واگذارشدهٔ رکوردهای معمولی را کامل می‌کند و fragment‌های آن‌ها را در همان reader پردازش می‌کند. برای TCPv4 GSO، reader کل aggregate را اعتبارسنجی می‌کند و مالکیت buffer آن را به worker انتخاب‌شده برای دریافت می‌دهد. worker مقصد packet‌های مستقل در اندازهٔ MTU را تخصیص می‌دهد، header و payload را کپی می‌کند و checksum‌های IPv4 و TCP را پیش از تحویل، ثبت log یا انتشار packet توسط TunDevice کامل می‌کند. batch‌های معمولی و aggregate‌های GSO در FIFO محدود مشترک هر worker قرار می‌گیرند؛ segmentation ناتمام در ابتدای صف می‌ماند و در callback‌های محدود ادامه پیدا می‌کند تا packet بعدی از آن جلو نزند. worker برای ظرفیت صف منتظر نمی‌ماند. رکوردهای معمولی GSO_NONE، از جمله IPv6، بدون الزام به IPv4 از reader عبور می‌کنند و همچنان به callback فعلی می‌رسند که غیر IPv4 را رد می‌کند. در framing معمولی TUN، packet unfragmented تولیدشده توسط kernel در این مرز trusted است؛ consumer بعدی policy اعتبارسنجی خود را حفظ می‌کند، مگر در جفت مستقیم receive trust که در ادامه آمده است. fragment IPv4 از parser مشترک fragment می‌گذرد و ساختار و header checksum آن پیش از association بررسی می‌شود؛ جفت مستقیم فقط بررسی header checksum را حذف می‌کند. در حالت reassemble، reader تا تکمیل datagram بایت‌ها را نگه می‌دارد؛ در حالت preserve-fragments مرز fragment و worker affinity حفظ می‌شود. buffer تحویل‌شده مالکیت معمول دارد و claim عمر fragment همراه آن نیست. ورودی‌های stack شرط کامل‌بودن datagram را هنگام دریافت enforce می‌کنند.

device-mtu یک بار هنگام ساخت تعیین و اعتبارسنجی می‌شود. هر نمونه MTU مستقل خود را نگه می‌دارد؛ تنظیم node‌های دیگر آن را تغییر نمی‌دهد. مقدار نامعتبر ارث‌رسیده از misc.mtu هسته به override صریح و معتبر همان node نیاز دارد.

در Linux خواندن TUN روی workerها انجام می‌شود. گزینهٔ multi-queue به‌طور پیش‌فرض true است؛ در این حالت برای interface جدید، برنامه یک صف برای هر worker و مسیریابی ثابت کرنل را امتحان می‌کند. اگر multiqueue یا مسیریابی لازم در دسترس نباشد، یک صف با مالکیت worker صفر استفاده می‌شود. اتصال به interfaceهای تک‌صف موجود همچنان پشتیبانی می‌شود. تعداد صف فعال در لاگ آغاز کار ثبت می‌شود و مستقل از گزینهٔ gso است. با multi-queue: false تنها یک صف با مالکیت worker صفر استفاده می‌شود؛ تعداد workerهای پردازش تغییر نمی‌کند؛ مصرف‌کننده مالکیت جریان را تعیین می‌کند. خواندن و نوشتن هر دو از مجموعهٔ صف‌های انتخاب‌شده استفاده می‌کنند.

برای TCP/UDP بدون fragmentation، انتخاب صف همچنان از hash متقارن آدرس‌ها، portها و پروتکل استفاده می‌کند و با worker پردازش جریان هماهنگ است. بسته‌های کامل و از نظر ساختار معتبر IPv4 با پروتکل‌های دیگر، به‌صورت round-robin تقریبی میان صف‌های دریافت پخش می‌شوند و پردازش را روی worker دریافت‌کننده ادامه می‌دهند. map مربوط به eBPF هر interface برای هر CPU کرنل یک شمارندهٔ ۳۲ بیتی جداگانه دارد که مقدار آن در محدودهٔ تعداد صف‌ها می‌ماند. افزایش معمولی شمارنده به عملیات اتمیک نیاز ندارد؛ offset مبتنی بر شناسهٔ CPU نیز باعث می‌شود همهٔ CPUها از صف صفر شروع نکنند. به‌روزرسانی‌های هم‌پوشان ممکن است یک گام افزایش را از دست بدهند؛ بنابراین ترتیب دقیق سراسری یا تعداد برابر بسته در صف‌ها تضمین نمی‌شود. نبود پشتیبانی map از نوع per-CPU یا steering، مسیر تک‌reader را فعال می‌کند. در حالت چندصفی، بسته‌های کامل روی worker خواننده ادامه می‌یابند. در حالت تک‌صف، packet کامل TCP/UDP با portهای قابل‌خواندن با hash متقارن جریان به یک worker ثابت می‌رود تا ترتیب ورودی همان جریان در FIFO حفظ شود. packetهای opaque یا غیرقابل‌تشخیص با شمارندهٔ round-robin همان session میان workerها تقسیم می‌شوند. مصرف‌کنندهٔ stateful همچنان مالک جریان را از tuple پس از تبدیل‌ها تعیین می‌کند؛ تنظیم جدید یا شناخت nodeهای بعدی در reader لازم نیست.

همهٔ fragmentهای IPv4 و TCP/UDP کوتاه‌تر از دو فیلد port، سیاست جفت آدرس و پروتکل قبلی را حفظ می‌کنند. هر datagram fragmentشده یک reader دارد، اما بسته‌های عادی و fragmentشدهٔ یک flow ممکن است reader متفاوت داشته باشند و جابه‌جا شوند. جدول مشترک fragment، نگهداری محدود و affinity حالت preserve-fragments را حفظ می‌کند؛ datagram کامل‌شده پس از reassembly از سیاست عادی بستهٔ کامل پیروی می‌کند. دریافت موازی نیز می‌تواند ترتیب بسته‌ها میان readerها را تغییر دهد. ورودی malformed یا نسخهٔ IP پشتیبانی‌نشده برای اعتبارسنجی بعدی همچنان به صف صفر می‌رود. در Windows نیز worker صفر با اعلان رویداد Wintun بسته‌ها را در batchهای محدود می‌خواند. Linux و Windows thread اختصاصی برای خواندن TUN ندارند. Windows و Darwin/BSD نیز برای TCP/UDP قابل‌تشخیص worker ثابت هر جریان و برای packetهای opaque شمارندهٔ محلی session را به کار می‌گیرند. مصرف‌کننده مالکیت جریان را پس از تبدیل‌ها تعیین می‌کند. backend مربوط به Darwin/BSD از thread خارجی با pool مستقل استفاده می‌کند. در Windows، macOS و BSD، صف دریافت هر device به ۸ MiB هزینهٔ واقعی تخصیص buffer و ۱۰۲۴ packet محدود است. پذیرش منتظر worker نمی‌ماند؛ ادامهٔ batch که جا نمی‌شود فوراً دور ریخته می‌شود و پس از آزادشدن ظرفیت، readهای بعدی دوباره پذیرفته می‌شوند. اندازهٔ batch جاری در Windows و macOS حداکثر ۱۲۸ packet است. در BSD این اندازه به پروفایل RAM بستگی دارد و در پروفایل server به ۲۵۶ packet می‌رسد. حافظهٔ batch جاری، metadata صف و ذخیرهٔ محدود fragment جدا از این بودجه حساب می‌شوند؛ ترتیب پذیرفته‌شدهٔ هر worker حفظ می‌شود.

رکورد معمولی offload در Linux، شامل DATA_VALID و NEEDS_CSUM مطابق با تضمین ورودی، فقط بایت‌های IP را در تخصیص best-fit کپی می‌کند. هزینهٔ همان تخصیص و metadata کار، یک slot و بدون سهم خروجی segmentation رزرو می‌شود؛ scratch نزد reader برای استفادهٔ دوباره می‌ماند. GSO سهم aggregate و خروجی و دو slot را حفظ می‌کند. جفت مستقیم trusted از TUN/PTC می‌تواند aggregate تضمین‌شدهٔ TCPv4 GSO را بدون segmentation تحویل دهد؛ سایر مسیرها segmentهای اندازهٔ MTU می‌گیرند. startup فقط هندسه و هزینهٔ scratch را نگه می‌دارد، نه یک buffer بلااستفاده.

جایگاه رایج​

TunDevice می‌تواند در ابتدا یا انتهای packet chain قرار بگیرد:

TunDevice -> IpManipulator -> RawSocket
UdpStatelessSocket -> WireGuardDevice -> TunDevice
TunDevice -> PacketsToStream -> TcpConnector
TcpListener -> StreamToPackets -> TunDevice

اگر TunDevice در انتهای chain نباشد، packet‌های خوانده‌شده از سیستم‌عامل در جهت upstream به node بعدی می‌روند. اگر node در انتهای chain باشد، همان packet‌ها در جهت downstream به node قبلی داده می‌شوند.

هر payloadی که از chain به TunDevice برسد، یک IP packet در نظر گرفته می‌شود و در TUN interface نوشته خواهد شد.

نمونه تنظیم​

{
"name": "tun0-adapter",
"type": "TunDevice",
"settings": {
"fragment-policy": "preserve-fragments",
"device-name": "wtun0",
"device-ip": "10.10.0.1/24",
"device-mtu": 1500,
"multi-queue": true,
"gso": true
},
"next": "next-node"
}

نمونه full-route با خارج کردن شبکه‌های داخلی:

{
"name": "tun0-adapter",
"type": "TunDevice",
"settings": {
"fragment-policy": "preserve-fragments",
"device-name": "wtun0",
"device-ip": "10.10.0.1/24",
"device-mtu": 1500,
"route-table": "main",
"route-exclude-cidrs": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"dns": ["1.1.1.1", "8.8.8.8"]
},
"next": "next-node"
}

تنظیمات ضروری​

فیلدنوعتوضیح
device-namestringنام TUN interface. این نام باید با interface‌های دیگر سیستم‌عامل یا node‌های دیگر WaterWall تداخل نداشته باشد.
device-ipstringآدرس IPv4 دستگاه به شکل CIDR، مثل 10.10.0.1/24. IPv6 رد می‌شود، زیرا مسیر packet فقط IPv4 را پردازش می‌کند.

object مربوط به settings الزامی است و نباید خالی باشد.

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

گزینهپیش‌فرضتوضیح
device-mtuMTU سراسری WaterWallمقدار MTU برای TUN device در بازهٔ inclusive ‏68..65535. عدد کسری، string، boolean و null رد می‌شوند.
multi-queuetrueگزینهٔ boolean برای فعال‌کردن تلاش ساخت یک صف TUN به‌ازای هر worker در Linux؛ در صورت نبود پشتیبانی لازم یا شکست ساخت صف‌های اضافی، یک صف با مالکیت worker صفر استفاده می‌شود. مقدار false راه‌اندازی multiqueue را غیرفعال می‌کند و از همان حالت تک‌صف استفاده می‌کند، بدون تغییر تعداد workerهای پردازش. مستقل از gso است. نوع‌های نامعتبر از جمله null در همهٔ پلتفرم‌ها رد می‌شوند؛ پلتفرم‌های غیر Linux مقدار boolean را می‌پذیرند و نادیده می‌گیرند.
kernel-queue-tuneدر Linux برابر true و در دیگر پلتفرم‌ها falseگزینهٔ boolean برای تنظیم صف دریافت کرنل هنگام startup. مقدار false طول صف موجود را تغییر نمی‌دهد، ولی گزارش تنظیمات همچنان ثبت می‌شود. مقدار معتبر در پلتفرم‌های دیگر اثری ندارد.
kernel-queue-lengthخودکار در صورت حذف گزینهتعداد packet در صف TUN لینوکس به‌ازای هر صف دریافت. عدد صحیح در بازهٔ 1..2147483647 با سقف INT_MAX؛ صفر به معنی خودکار نیست. صف موجودِ بزرگ‌تر حفظ می‌شود. فقط با kernel-queue-tune: true اعمال می‌شود.
gsotrueدرخواست TCPv4 GSO در Linux. مقدار false segmentation را غیرفعال می‌کند، ولی هر device تازه همچنان checksum offload را خودکار امتحان می‌کند. نوع‌های نامعتبر از جمله null رد می‌شوند. رد شدن GSO حالت checksum-only را حفظ می‌کند؛ نبود پشتیبانی checksum/framing یا منابع لازم باعث raw fallback می‌شود. حالت نهایی در startup log ثبت می‌شود. MTU همچنان اندازهٔ هر packet خروجی segmentation را محدود می‌کند.
route-tableoffنصب route‌های native سیستم‌عامل را کنترل می‌کند. مقدارهای off، main، auto یا table name/number لینوکس.
system-routefalseمیان‌بری برای full-device routing. اگر true باشد و route-table را ننویسید، مانند route-table: "main" عمل می‌کند.
route-cidrs0.0.0.0/0CIDRهای IPv4 که هنگام فعال بودن routing از TUN عبور می‌کنند. وجود هر IPv6، حتی در فهرست مختلط یا پس از exclusion، ساخت node را پیش از نصب route رد می‌کند.
route-exclude-cidrsنداردCIDR‌هایی که از route-cidrs کم می‌شوند؛ مناسب LAN، شبکه مدیریت و endpoint‌های upstream.
dnsتنظیم نشدهیک یا دو DNS server از نوع IPv4 برای TUN interface.
loop-protectionوقتی system routing فعال است، فعال می‌شودنمی‌گذارد socket‌های outbound خود WaterWall دوباره از TUN عبور کنند.
post-up-scriptنداردcommandی که پس از بالا آمدن interface، نصب route‌ها و تنظیم DNS اجرا می‌شود.
pre-down-scriptنداردcommandی که پیش از پاک شدن DNS و route‌ها و پایین آوردن interface اجرا می‌شود.

دو گزینهٔ صف کرنل در همهٔ پلتفرم‌ها و حتی هنگام غیرفعال بودن تنظیم خودکار، اعتبارسنجی می‌شوند. kernel-queue-tune باید boolean باشد. برای kernel-queue-length عدد کسری، منفی، صفر، string، boolean و null رد می‌شوند؛ فقط حذف گزینه انتخاب خودکار را فعال می‌کند.

modifier مربوط به ECN در رکورد TCPv4 GSO لینوکس نیز پشتیبانی می‌شود.

interface‌های از پیش ساخته‌شده در Linux: درخواست GSO به یک نام interface جدید نیاز دارد. اگر تنظیمات شما به TUN از پیش ساخته‌شده متصل می‌شود، باید "gso": false را صریحاً تنظیم کنید تا رفتار اتصال معمولی حفظ شود؛ این نکته شامل تنظیماتی هم می‌شود که قبلاً گزینهٔ gso را نداشتند. fallback خودکار هنگام در دسترس نبودن امکانات offload به معنای پذیرش تداخل نام interface نیست.

اندازهٔ صف دریافت کرنل در Linux​

پس از ساخت صف‌ها و هر fallback به حالت تک‌صف، kernel-queue-tune هدف اولیهٔ txqueuelen را با تعداد نهایی صف‌های دریافت تعیین می‌کند:

RAM profile تنظیم‌شدهتعداد خودکار packet در هر صف
S1 / S21,024
M1 (client)2,048
M2 (client-larger)4,096
L1 / L2 (server)8,192

هدف خودکار به 131072 / active_queue_count با تقسیم صحیح محدود می‌شود. kernel-queue-length جای هدف خودکار را می‌گیرد و مشمول این سقف نیست. هیچ‌کدام txqueuelen موجودِ بزرگ‌تر را کاهش نمی‌دهد. profile بیانگر سیاست حافظهٔ تنظیم‌شده است و با تشخیص RAM میزبان انتخاب نمی‌شود. هر TUN بودجهٔ مستقل دارد.

این تنظیم best effort است: رد شدن تغییر یا در دسترس نبودن query یک هشدار ایجاد می‌کند و صف‌های قابل‌استفاده همچنان کار می‌کنند. startup تعداد صف‌های فعال، منبع هدف (auto، override یا disabled) و طول قبلی، درخواستی و خوانده‌شده از کرنل را ثبت می‌کند؛ مقدارهای غیرقابل‌خواندن مشخص می‌شوند. این گزارش با غیرفعال بودن تنظیم نیز ثبت می‌شود.

این گزینه مربوط به packetهای منتظر خواندن WaterWall در کرنل است. FIFO مشترک userspace، اندازهٔ batchهای خواندن و نوشتن و سقف backlog خروجی جدا هستند. تعداد صف یا packet نشان‌دهندهٔ RSS تخصیص‌یافته یا تعداد بایت payload نیست. صف بزرگ‌تر می‌تواند burst را جذب کند، ولی مصرف حافظه و تأخیر را هم افزایش دهد و تضمینی برای throughput نیست. این تنظیم sysctl سراسری جدیدی نمی‌نویسد و از misc.tcp-tune در core مستقل است.

معنی آدرس TUN​

اگر این مقدار را تنظیم کنید:

10.10.0.1/24

آدرس 10.10.0.1 متعلق به interface خود سیستم‌عامل است. ارسال packet یا ping به همین آدرس معمولاً مانند دسترسی به یک آدرس local رفتار می‌کند و مسیر مورد انتظار در WaterWall را درگیر نمی‌کند.

برای فرستادن packet به WaterWall، معمولاً باید آدرس دیگری از همان range را هدف بگیرید:

10.10.0.2

در این حالت packet وارد TUN می‌شود و WaterWall آن را می‌بیند؛ برای مثال source می‌تواند 10.10.0.1 و destination برابر 10.10.0.2 باشد. پس از TunDevice نیز می‌توانید با node‌هایی مانند IpOverrider یا IpManipulator آدرس‌ها و header‌های packet را تغییر دهید.

تنظیم route​

نصب route در سیستم‌عامل به‌طور پیش‌فرض غیرفعال است. برای فعال کردن آن یکی از این دو روش را به کار ببرید:

"route-table": "main"

یا:

"system-route": true

مقدارهای route-table:

مقداررفتار
offهیچ route‌ای نصب نمی‌شود.
mainroute‌ها در جدول اصلی سیستم نصب می‌شوند.
autoدر پلتفرم‌های پشتیبانی‌شده مثل main-table routing رفتار می‌کند.
نام یا شماره table در Linuxroute‌ها در همان جدول لینوکس نصب می‌شوند.

در Windows و macOS، مقدارهای غیر از main یا auto رد می‌شوند.

اگر routing فعال باشد و route-cidrs را ننویسید، WaterWall مسیر کامل IPv4 می‌سازد. با route-table: off فهرست capture نصب نمی‌شود. exclusion از خانوادهٔ دیگر مسیر IPv4 را تغییر نمی‌دهد؛ وجود IPv6 در فهرست capture فعال همیشه رد می‌شود، حتی اگر exclusion آن را حذف می‌کرد. default route مانند 0.0.0.0/0 به دو route از نوع /1 تقسیم می‌شود تا default route فعلی سیستم مستقیماً جایگزین نشود.

با route-exclude-cidrs می‌توانید شبکه‌هایی را از route‌ها کم کنید:

{
"route-table": "main",
"route-cidrs": ["0.0.0.0/0"],
"route-exclude-cidrs": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
]
}

شبکه داخلی، مسیر مدیریت server و endpoint اصلی upstream را حتماً از route‌های TUN مستثنا کنید.

Loop Protection​

با فعال شدن system route یا full route روی TUN، ممکن است سیستم‌عامل socket‌های outbound خود WaterWall را هم دوباره به TUN بفرستد. نتیجه یک self-loop است: WaterWall packet را می‌فرستد، سیستم‌عامل آن را به TUN برمی‌گرداند و WaterWall همان packet را دوباره می‌خواند.

وقتی system routing فعال باشد، loop-protection نیز به‌طور پیش‌فرض روشن است. node پیش از نصب route‌های TUN، interface فیزیکی پیش‌فرض را پیدا می‌کند و آن را به‌عنوان egress pin سراسری process منتشر می‌سازد. adapter‌هایی مانند TcpConnector، UdpConnector و UdpStatelessSocket از این pin استفاده می‌کنند، مگر اینکه interface خودشان صریحاً تنظیم شده باشد.

پلتفرمرفتار
Linuxاز interface پیش‌فرض detect شده برای bind کردن socket استفاده می‌شود.
Windowsاز index interface تشخیص‌داده‌شده برای مسیر outbound socket استفاده می‌شود.
macOSegress pin خودکار فعلاً وجود ندارد؛ از interface صریح در connector‌ها یا route exclusion استفاده کنید.

تنظیم صریح interface در adapter بر pin خودکار اولویت دارد؛ source-ip به‌تنهایی این pin را کنار نمی‌زند. اگر IP مبدأ انتخاب‌شده روی interface فیزیکی دیگری غیر از مسیر پیش‌فرض تشخیص‌داده‌شده قرار دارد، interface را در connector صریحاً تنظیم کنید.

socket‌های DNS resolver در c-ares با این قابلیت pin نمی‌شوند. اگر مسیر فیزیکی پیش‌فرض host پس از startup تغییر کرد، WaterWall را restart کنید یا interface‌ها و route‌ها را صریح تنظیم کنید.

Reverse Path Filtering (Linux)​

reverse-path filtering در Linux، packet‌های ورودی‌ای را دور می‌ریزد که آدرس مبدأشان از همان interfaceی که packet روی آن رسیده برنمی‌گردد. ترافیکی که به TUN تزریق می‌شود مرتباً به این بررسی برمی‌خورد، بنابراین TunDevice روی Linux مقدار 0 را در هر دو مسیر زیر می‌نویسد:

/proc/sys/net/ipv4/conf/all/rp_filter
/proc/sys/net/ipv4/conf/<device-name>/rp_filter

هر دو لازم‌اند. kernel بر اساس max(all, <interface>) فیلتر می‌کند، پس اگر یکی از این دو غیرصفر بماند، فیلتر برای TUN همچنان فعال می‌ماند.

این کار برای هر TUN روی Linux انجام می‌شود، نه فقط وقتی system route فعال باشد. TUNی که routeهایش از بیرون نصب می‌شوند — با post-up-script، با ابزاری دیگر، یا از سمت peer — دقیقاً به همین بررسی برمی‌خورد.

فایل rp_filter مربوط به سایر interface‌ها دست‌نخورده می‌ماند، پس هر interface که مقدار غیرصفر خودش را دارد همچنان فیلتر می‌شود. یک اثر جانبی را در نظر داشته باشید: interfaceی که مقدار خودش 0 است و فقط all غیرصفر بوده، بدون فیلتر می‌شود، چون پایین آوردن all کف مقدار را برای همه interface‌ها پایین می‌آورد.

این تغییرهای sysctl هنگام stop یا destroy برگردانده نمی‌شوند. اگر فایل‌های sysctl فقط‌خواندنی باشند، startup یک warning ثبت می‌کند و ادامه می‌دهد.

چرا مقدار دوباره اعمال می‌شود​

روی توزیع‌های systemd، یک‌بار نوشتن مقدار interface دوام نمی‌آورد. udev در پاسخ به رویداد add مربوط به interface جدید این دستور را اجرا می‌کند:

systemd-sysctl --prefix=/net/ipv4/conf/<device-name> ...

که الگوهایی مانند خط net.ipv4.conf.*.rp_filter در /usr/lib/sysctl.d/50-default.conf را دوباره اعمال می‌کند. این اجرا چند میلی‌ثانیه پس از ظاهر شدن interface انجام می‌شود، یعنی درست بعد از نوشته شدن مقدار، و در غیر این صورت آن را بازنویسی می‌کرد.

به همین دلیل TunDevice مقدار را نگه می‌دارد و آن‌قدر دوباره اعمال می‌کند تا ۳۰۰ میلی‌ثانیه روی 0 بماند. تقریباً همین مقدار به زمان بالا آمدن TUN اضافه می‌شود. اگر مقدار مدام بازنویسی شود، WaterWall پس از ۲ ثانیه منصرف می‌شود و یک error ثبت می‌کند؛ در آن حالت reverse-path filtering برای آن device فعال می‌ماند.

تنظیم DNS​

dns یک یا دو IPv4 address می‌پذیرد:

"dns": ["1.1.1.1", "8.8.8.8"]

آدرس IPv6، hostname، string خالی یا بیش از دو server هنگام ساخت node پذیرفته نمی‌شود. مقدار null نیز مانند تنظیم نکردن گزینه است.

پلتفرمرفتار
Linuxبا resolvectl dns و domain مسیر ~. تنظیم می‌شود. نیازمند systemd-resolved و resolvectl است.
Windowsبا netsh روی Wintun adapter، static IPv4 DNS تنظیم می‌شود. precedence همچنان تابع metric‌های Windows است.
macOSگزینه dns در زمان ساخت node رد می‌شود؛ از post-up-script و pre-down-script استفاده کنید.

مسیر packet​

هنگام راه‌اندازی، TunDevice:

  1. مشخص می‌کند packet‌های خوانده‌شده باید به next بروند یا prev
  2. TUN interface را می‌سازد
  3. device-ip را روی آن قرار می‌دهد
  4. interface را بالا می‌آورد
  5. روی Linux، reverse-path filtering را برای device غیرفعال می‌کند
  6. route‌ها را در صورت فعال بودن نصب می‌کند
  7. DNS را در صورت تنظیم بودن اعمال می‌کند
  8. post-up-script را در صورت وجود اجرا می‌کند

وقتی device سیستم‌عامل packet تولید کند:

  1. packet روی یک worker دریافت می‌شود
  2. اگر device پایین باشد یا برنامه در حال پایان یافتن باشد، packet دور ریخته می‌شود
  3. نسخه IP بررسی می‌شود
  4. packet‌های IPv4 با worker packet line جلو فرستاده می‌شوند
  5. packet‌های غیر IPv4 در مسیر دریافت فعلی دور ریخته می‌شوند

وقتی payload از chain به TunDevice برسد:

  1. payload به‌عنوان IP packet در نظر گرفته می‌شود
  2. payload به‌عنوان دقیقاً یک packet کامل IPv4 validation می‌شود؛ بایت‌های اضافی در انتها، header ناقص و طول ناسازگار drop می‌شوند
  3. اگر buffer همان packet درخواست کرده باشد، checksum دوباره محاسبه می‌شود؛ failure درخواست همان buffer را مصرف و آن را recycle می‌کند و writer صدا زده نمی‌شود
  4. packet معتبر داخل TUN device نوشته می‌شود
  5. اگر device پایین باشد یا write ناموفق شود، buffer بازیافت و warning در log ثبت می‌شود

packet line​

TunDevice از packet line هر worker استفاده می‌کند، نه line معمولی per-connection. packet line‌ها نباید در runtimeی عادی از بین بروند.

در build‌های debug، node پس از فرستادن packet ورودی بررسی می‌کند که packet line هنوز زنده باشد. اگر tunnel دیگری آن را در runtime از بین برده باشد، WaterWall این اتفاق را نقض قرارداد packet tunnel می‌داند.

callback‌های Init، ‏Est، ‏Pause و Resume terminal absorber‌های بدون state هستند. اما Finish غیرمنتظره در هر جهت نقض fatal lifecycle مربوط به packet line است و program را abort می‌کند؛ no-op نیست. این node packet line‌ها را هرگز destroy نمی‌کند و line state آن صفر بایت است.

checksum​

بعضی packet tunnel‌ها با sbufSetRecalculateChecksum(buf, true) روی buffer همان packet درخواست ترمیم checksum می‌گذارند. TunDevice پیش از نوشتن به سیستم‌عامل درخواست آن buffer را مصرف و checksum را اصلاح می‌کند. هر packet درخواست مستقل دارد؛ نگه‌داری یا تحویل packet دیگری درخواست آن را تغییر نمی‌دهد. مقدار false تضمین معتبر بودن checksum نیست.

برای IPv4 fragment‌ها، helper مربوطه transport checksum را که از یک fragment کامل قابل محاسبه نیست با احتیاط مدیریت می‌کند و فقط بخش‌هایی را اصلاح می‌کند که از داده موجود قابل اصلاح باشند.

درخواست checksum حتی روی drop path نیز روی همان buffer در این مرز مصرف می‌شود. اگر ساختار موجود برای calculation کافی نباشد، packet با warning rate-limited drop و هرگز به TUN نوشته نمی‌شود.

این درخواست با تضمین checksum در metadata خصوصی TUN، حالت trusted و offload/GSO مستقل است. حالت trusted درخواست صریح ترمیم را حذف نمی‌کند و درخواست نیز شروط اعتبارسنجی ساختار یا offload را تغییر نمی‌دهد.

نکته‌های MTU​

اگر device-mtu را تنظیم نکنید، MTU سراسری WaterWall به کار می‌رود.

کم کردن MTU زمانی مفید است که node‌های بعدی overhead اضافه می‌کنند. اما اگر کنار adapter‌هایی مثل RawSocket کار می‌کنید که با interface فیزیکی مستقیم سروکار دارند، MTU را با path واقعی هماهنگ نگه دارید؛ در غیر این صورت سیستم‌عامل ممکن است packet‌ها را رد کند.

shutdown​

هنگام stop یا destroy، cleanup با ترتیب معکوس انجام می‌شود:

  1. pre-down-script را قبل از down کردن device اجرا می‌کند
  2. egress pin مربوط به loop protection را پاک می‌کند
  3. DNS را پاک می‌کند
  4. route‌های نصب‌شده را به ترتیب معکوس حذف می‌کند
  5. TUN device را پایین می‌آورد و از بین می‌برد

اگر پاک‌سازی route یا DNS ناموفق باشد، warning در log ثبت می‌شود.

خطاهای رایج​

  • ping کردن خود آدرس TUN و انتظار اینکه packet وارد WaterWall شود. یک آدرس دیگر در range route شده را هدف بگیرید.
  • فعال کردن full routing بدون exclude کردن endpoint سرور upstream، یا تکیه بر loop protection در پلتفرم یا topology‌ای که این قابلیت پوشش نمی‌دهد.
  • استفاده از dns روی macOS. برای آن از script استفاده کنید.
  • انتظار forward شدن packet‌های غیر IPv4 از مسیر receive فعلی.
  • رفتار دادن به TunDevice مثل TCP/UDP adapter اتصال‌محور. این node raw IP packet را از packet line عبور می‌دهد.
  • فراموش کردن نیاز به دسترسی administrator برای مدیریت TUN روی Windows.

متادیتای node​

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

ویژگیمقدار
node flagskNodeFlagChainHead | kNodeFlagChainEnd | kNodeFlagSupportsTrustedPacketChecksums | kNodeFlagPacketPayloadEnqueueOnly
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer3
layer_group_prev_nodekNodeLayer3
layer_group_next_nodekNodeLayer3
required_padding_left۱۰ بایت در Linux؛ ۰ بایت در سایر پلتفرم‌های پشتیبانی‌شده

پرچم‌های checksum و enqueue-only قابلیت‌های یک جفت مستقیم TunDevice–PacketsToConnection را اعلام می‌کنند. جفت واجد شرایط در Linux، اعتماد ورودی را هنگام setup حتی در حالت raw خودکار فعال می‌کند. checksum offload خروجی علاوه بر این به پشتیبانی مذاکره‌شده و منابع آماده نیاز دارد؛ پرچم‌های ثابت به‌تنهایی به معنی فعال بودن هیچ‌یک از این حالت‌ها نیستند.

این مقدار نیاز منطقی اعلام‌شدهٔ node به فضای خالی سمت چپ است. در Linux حتی با "gso": false نیز همین مقدار اعلام می‌شود. هم‌ترازی تخصیص حافظه موضوع جداگانه‌ای است؛ بنابراین هر تخصیص لزوماً دقیقاً ده بایت بزرگ‌تر نمی‌شود.

fragment-policy (اختیاری، پیش‌فرض: reassemble)​

حذف این تنظیم مقدار reassemble را انتخاب می‌کند: fragment‌های IPv4 پیش از تحویل به chain بازسازی می‌شوند. preserve-fragments بایت‌ها و مرز fragment‌ها را حفظ می‌کند. برای این policy فهرست مجاز نام node یا topology وجود ندارد؛ validation عادی لایه‌ها و chain همچنان برقرار است. مقدار نامعتبر، از جمله null، startup را رد می‌کند.

ورودی packet در جهت upstream برای PacketsToConnection و در جهت downstream برای ConnectionToPackets فقط datagram کامل IPv4 می‌پذیرد. رسیدن fragment به هر یک نقض قرارداد runtime است و با پیام fatal دربارهٔ نیاز به reassembly، کل برنامه در Debug و Release خاتمه می‌یابد. پیش از این consumer‌ها reassembly لازم است، مستقل از policy دستگاه یا node‌های میانی. بیت DF به‌تنهایی fragment نیست؛ MF یا offset غیرصفر نشانهٔ fragmentation است.

بازسازی تا ۶۵٬۵۳۵ بایت را تحویل می‌دهد، ولی سقف خروجی packet یا MTU را بالا نمی‌برد و fragmentation دوبارهٔ خروجی را خودکار انجام نمی‌دهد. محدودیت‌های نگهداری و مالکیت packet در بخش چهارم راهنمای توسعه‌دهنده آمده است.

checksum در جفت مستقیم TunDevice و PacketsToConnection​

اتصال مستقیم و دوطرفهٔ TunDevice -> PacketsToConnection در Linux، checksum ورودی header مربوط به IPv4 و TCP/UDP را خودکار معتبر فرض می‌کند؛ این رفتار در حالت raw، checksum-only و GSO و مستقل از gso فعال است و تنظیم اضافی ندارد. سیستم‌عامل دیگر و node واسط اعتبارسنجی عادی را حفظ می‌کنند. فرض اعتماد به ورودی kernel در بخش چهارم راهنمای توسعه‌دهنده توضیح داده شده است.

در TUN، PTC و fake DNS، بررسی checksum ورودی IPv4 و TCP/UDP، از جمله ورودی بدون تضمین metadata و پذیرش fragment، حذف می‌شود. بررسی ساختار، سازگاری metadata، شرایط fragment، درخواست repair، بودجهٔ FIFO و مالکیت هنگام shutdown تغییر نمی‌کند. header تولیدشده و بازسازی‌شده همچنان checksum معتبر دارد. بررسی IPv6 و UDP-Lite فعال می‌ماند.

metadata معتبر NEEDS_CSUM با مختصات مطابق یا DATA_VALID قابل اعمال، تضمین مثبت را فقط در کار خصوصی TUN و FIFO نگه می‌دارد؛ اعتماد ورودی به packet بدون تضمین چنین نشانه‌ای اضافه نمی‌کند. metadata متناقض و fragment با checksum ناقص رد می‌شود. قواعد تبدیل GSO تغییر نمی‌کند.

اعتماد ورودی checksum خروجی را partial نمی‌کند. خروجی raw همچنان checksum کامل نرم‌افزاری و checksum-on-copy دارد. فقط پس از مذاکرهٔ واقعی checksum offload و آماده شدن منابع آن، PTC برای packet کامل و مناسب MTU از محاسبهٔ transport checksum صرف‌نظر می‌کند و TUN آن را با seed صحیح pseudoheader و metadata NEEDS_CSUM به kernel می‌دهد. datagram بزرگ پیش از fragmentation checksum کامل می‌گیرد و fragment‌ها metadata عادی دارند. حالت‌های انتخاب‌شده تا destroy ثابت می‌مانند.

خروجی packet در PTC با یک کپی از chain pbuf به sbuf مستقل و best-fit با padding لازم ساخته می‌شود. فقط TunDevice مجاور و دوطرفه با قابلیت kNodeFlagPacketPayloadEnqueueOnly روی worker همان packet line تحویل مستقیم دارد؛ سایر همسایه‌ها و callback‌های normal-line همچنان deferred هستند. callback دستگاه فقط validation و enqueue بدون انتظار در FIFO موجود یا drop را انجام می‌دهد و نباید وارد engine، event loop یا callback node دیگری شود.

در جفت مستقیم trusted روی Linux با framing فعال، writer تنها TCP data‌های مجاور و سازگار را به یک GSO record با header خصوصی و payload iovec‌های buffer‌های اصلی تبدیل می‌کند. هر worker FIFO خروجی و flush deferred خودش را دارد؛ thread یا pool اختصاصی writer وجود ندارد. workerها می‌توانند هم‌زمان بنویسند؛ ترتیب در FIFO همان worker حفظ می‌شود. هر flush حداکثر ۶۴ packet و ۲۵۶ KiB هزینهٔ تخصیص را پردازش می‌کند؛ یک تخصیص بزرگ‌تر به‌تنهایی پیش می‌رود و باقیمانده دوباره زمان‌بندی می‌شود. طول IPv4 هر record حداکثر ۶۵۵۳۵ بایت است. هر packet اولیه همچنان باید در MTU جا شود. UDP، fragment، control، packet ناسازگار، node میانی و gso:false مسیر عادی را دارند. کپی تجمیعی payload ساخته نمی‌شود. stop ابتدا پذیرش را می‌بندد و منتظر callbackهای پذیرفته‌شده می‌ماند، سپس bufferهای باقی‌مانده را آزاد می‌کند.