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

UdpListener

UdpListener یک adapter سرور UDP است. این node یک آدرس و پورت محلی UDP را bind می‌کند، datagram‌های ورودی را بر اساس peer راه دور گروه‌بندی می‌کند، به‌ازای هر peer یک line در WaterWall می‌سازد و payload‌ها را در جهت upstream به node بعدی می‌فرستد.

جایگاه معمول:

client -> UdpListener -> ... -> UdpConnector -> remote UDP service

این node معمولاً ابتدای یک chain UDP به‌شکل stream قرار می‌گیرد.

چه کاری انجام می‌دهد؟​

  • روی یک پورت UDP، چند پورت مشخص یا یک بازه‌ی پیوسته از پورت‌ها گوش می‌دهد.
  • datagram‌های UDP ورودی را از طریق socket manager در WaterWall دریافت می‌کند.
  • filter‌های اختیاری listener، مانند whitelist، ‏blacklist و balance group‌ها را اعمال می‌کند.
  • به‌ازای هر ترکیب آدرس و پورت peer راه دور، یک line در WaterWall می‌سازد.
  • datagram‌های ورودی را به‌صورت payload در upstream به node بعدی می‌فرستد.
  • payload‌های downstream را به آدرس peer ذخیره‌شده بازمی‌گرداند.
  • line‌های peer را با idle timeout مربوط به UDP ردیابی می‌کند.

UDP ذاتاً Connectionless است، اما UdpListener برای بقیه‌ی chain به‌ازای هر peer یک line پایدار فراهم می‌کند.

نمونه‌ی config​

{
"name": "udp-listener",
"type": "UdpListener",
"settings": {
"address": "0.0.0.0",
"port": [
5353,
853,
123
],
"large-send-buffer": true,
"large-recv-buffer": true,
"interface": "eth0",
"fwmark": 10,
"balance-group": "udp-public",
"balance-interval": 30000,
"multiport-backend": "socket",
"whitelist": [
"192.168.1.0/24",
"2001:db8::/64"
],
"blacklist": [
"192.168.1.50/32"
]
},
"next": "next-node-name"
}

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

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

فیلدنوعتوضیح
namestringنامی که کاربر برای node انتخاب می‌کند و باید در فایل config یکتا باشد.
typestringباید دقیقاً "UdpListener" باشد.
nextstringنام node‌ای که line‌ها و payload‌های peer‌های UDP را دریافت می‌کند.

فیلدهای الزامی در settings:

فیلدنوعتوضیح
addressstringآدرس محلی برای bind، مانند "0.0.0.0"، ‏"::" یا یک IP محلی مشخص.
port یا port-rangenumber، ‏array یا range arrayتعریف پورت listen؛ وجود دقیقاً یکی از این دو فیلد الزامی است.

settings باید یک object غیرخالی باشد.

تنظیم پورت​

UdpListener سه حالت برای پورت دارد.

یک پورت​

{
"settings": {
"address": "0.0.0.0",
"port": 5353
}
}

فهرست صریح پورت‌ها​

{
"settings": {
"address": "0.0.0.0",
"port": [
5353,
853,
123
]
}
}

در این حالت فقط روی پورت‌های نوشته‌شده گوش داده می‌شود؛ این array به‌معنای بازه‌ی 123 تا 5353 نیست.

بازه‌ی پیوسته‌ی پورت‌ها​

{
"settings": {
"address": "0.0.0.0",
"port-range": [
20000,
20100
]
}
}

port-range باید array‌ای شامل دقیقاً دو پورت integer مثبت به‌شکل [min, max] باشد. مقدار کمینه باید کوچک‌تر یا مساوی مقدار بیشینه باشد.

قواعد:

قاعدهرفتار
استفاده‌ی هم‌زمان از port و port-rangeراه‌اندازی متوقف می‌شود.
مقدار number برای portروی یک پورت UDP گوش می‌دهد.
مقدار array برای portروی پورت‌های صریح موجود در array گوش می‌دهد.
مقدار array برای port-rangeروی همه‌ی پورت‌های UDP از مقدار کمینه تا بیشینه گوش می‌دهد.
پورت نامعتبر، صفر، منفی یا بیشتر از 65535راه‌اندازی متوقف می‌شود.
هشدار

از بازه‌های قدیمی با قالب string، مانند "20000-20100"، استفاده نکنید. parser فعلی برای port-range یک array دومقداری انتظار دارد.

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

تنظیمنوعمقدار پیش‌فرضتوضیح
large-send-bufferboolean یا integer مثبتtrueمقدار SO_SNDBUF را روی socket‌های listener نوع UDP تنظیم می‌کند.
large-recv-bufferboolean یا integer مثبتtrueمقدار SO_RCVBUF را روی socket‌های listener نوع UDP تنظیم می‌کند.
interfacestringتنظیم نشدهlistener را به یک interface محلی شبکه محدود می‌کند.
fwmarkintegerتنظیم نشدهدر صورت پشتیبانی، یک socket mark به سبک Linux اعمال می‌کند.
balance-groupstringتنظیم نشدهlistener‌های سازگار و هم‌پورت را برای توزیع پایدار peer‌ها در یک گروه قرار می‌دهد.
balance-intervalinteger، بر حسب میلی‌ثانیهمقدار پیش‌فرض socket managerمدت حفظ انتخاب قبلی برای peer‌های تکراری در balance group.
multiport-backendstringمقدار پیش‌فرض runtime برای port-rangebackend مربوط به بازه‌ی پیوسته‌ی پورت‌ها: "iptables" یا "socket".
whitelistarray‌ای از string‌هاتنظیم نشدهIP‌ها یا بازه‌های CIDR مجاز برای peer.
blacklistarray‌ای از string‌هاتنظیم نشدهIP‌ها یا بازه‌های CIDR مسدود برای peer.

گزینه‌های socket buffer​

large-send-buffer و large-recv-buffer مقادیر زیر را می‌پذیرند:

مقدارمفهوم
trueاز اندازه‌ی پیش‌فرض WaterWall برای socket buffer بزرگ استفاده می‌کند که در حال حاضر 16777216 byte است.
falseاین socket buffer را صریحاً تنظیم نمی‌کند و مقدار پیش‌فرض Kernel به کار می‌رود.
integer مثبتهمان تعداد byte را مستقیماً درخواست می‌کند.

هر دو گزینه برای listener‌های UDP به‌صورت پیش‌فرض از socket buffer بزرگ WaterWall استفاده می‌کنند. همین parser برای socket‌های association پویا هم اعمال می‌شود: false مقدار پیش‌فرض Kernel را برای آن‌ها نیز دست‌نخورده می‌گذارد و buffer بزرگ WaterWall درخواست نمی‌کند.

‏interface و fwmark​

interface، ‏listener را به یک دستگاه محلی شبکه محدود می‌کند.

در Linux، ‏WaterWall از SO_BINDTODEVICE استفاده می‌کند. در platform‌هایی که bind به device را پشتیبانی نمی‌کنند، WaterWall در صورت امکان روی آدرس IPv4 همان interface bind می‌شود.

روی socket متصل به device، انتخاب فقط میان listenerهای همان interface و listenerهای بدون محدودیت interface انجام می‌شود. listener مربوط به interface دیگر، حتی با اولویت بالاتر ACL یا انتخاب ذخیره‌شده‌ی balance، واجد شرایط نیست. listenerهای بدون محدودیت همچنان بر اساس اولویت معمول آدرس و ACL و ترتیب ثبت رقابت می‌کنند؛ محدودیت interface اولویت تازه‌ای ایجاد نمی‌کند. socket بدون محدودیت، interface ورودی مشخصی ندارد و نمی‌تواند listener محدود به یک interface را انتخاب کند. در platformهای دیگر، انتخاب بر اساس آدرس مؤثر bind همان interface انجام می‌شود. انتخاب پایدار balance علاوه بر IP کلاینت، آدرس محلی و پورت مقصد منطقی، بر اساس protocol و محدوده‌ی مؤثر interface نیز جدا می‌شود.

fwmark در platform‌های پشتیبانی‌شده از SO_MARK استفاده می‌کند. این قابلیت به platform وابسته است و در Windows در دسترس نیست.

filter و توزیع​

پیش از آنکه datagram به UdpListener برسد، socket manager مشترک عملیات filter و توزیع را انجام می‌دهد.

تنظیمرفتار
whitelistدر صورت تنظیم، فقط peer‌هایی پذیرفته می‌شوند که IP آن‌ها با فهرست منطبق باشد.
blacklistpeer‌هایی را که IP آن‌ها با فهرست منطبق است رد می‌کند.
balance-groupتوزیع پایدار peer‌ها را میان listener‌های سازگار روی یک پورت فعال می‌کند.
balance-intervalمشخص می‌کند peer چه مدت به listener انتخاب‌شده اختصاص داده شود.
multiport-backendروش پیاده‌سازی listener دارای port-range پیوسته را تعیین می‌کند.

اگر هر دو ACL تنظیم شده باشند، peer باید، در صورت وجود Whitelist، با آن منطبق باشد و نباید با Blacklist تطابق داشته باشد.

نمونه:

{
"settings": {
"address": "0.0.0.0",
"port": 5353,
"whitelist": [
"10.0.0.0/8",
"203.0.113.10/32",
"2001:db8::/64"
],
"blacklist": [
"10.0.0.99/32"
]
}
}

مدل یک line به‌ازای هر peer​

UDP اتصال transport ندارد؛ بنابراین UdpListener برای chain یک مدل Pseudo-connection می‌سازد.

برای هر ترکیب یکتای آدرس و پورت peer راه دور روی یک socket در حال listen:

  1. با نخستین datagram پذیرفته‌شده، یک line تازه در WaterWall ساخته می‌شود.
  2. آدرس peer در line state مربوط به listener ذخیره می‌شود.
  3. datagram‌های بعدی همان peer تا زمان انقضای line از آن استفاده می‌کنند.
  4. payload‌های downstream روی آن line به peer ذخیره‌شده بازگردانده می‌شوند.

به این ترتیب، node‌های stream-style در ادامه‌ی chain می‌توانند با callback‌های عادی line در WaterWall روی ترافیک UDP کار کنند.

جریان داده​

از peer به chain:

UDP datagram -> UdpListener -> upstream payload -> next node

از chain به peer:

previous downstream payload -> UdpListener -> UDP send to remembered peer

source context مربوط به line از آدرس socket سمت peer پر و به‌عنوان UDP علامت‌گذاری می‌شود.

معنای Establishment​

line جدید peer با نخستین datagram پذیرفته‌شده ساخته می‌شود، اما فقط زمانی Established خواهد شد که callback مربوط به est در downstream از node بعدی به UdpListener برسد.

رفتار Pause​

وقتی line مربوط به peer در حالت Pause است، UdpListener، ‏datagram‌های ورودی را در صف نگه نمی‌دارد.

اگر datagramی برای یک line در حالت Pause برسد:

  • datagram دور ریخته می‌شود؛
  • line زنده می‌ماند؛
  • پس از Resume، ‏datagram‌های تازه‌ی همان peer دوباره قابل Forward هستند.

این رفتار با Backpressure شبیه TCP متفاوت است. در این node، ‏Pause یعنی دورریختن داده، نه نگه‌داشتن آن در buffer.

idle timeout​

هر line مربوط به peer در یک جدول idle ردیابی می‌شود.

timeout‌های فعلی:

وضعیتtimeout
line تازه‌ساخته‌شده برای peerحدود 30 seconds
peer فعال پس از ادامه‌ی ترافیکحدود 300 seconds

اگر line مربوط به peer منقضی شود، UdpListener یک finish در upstream می‌فرستد و line‌ای را که خودش ساخته است نابود می‌کند.

ارائه‌دهندهٔ endpoint پویا​

UdpListener یک provider تایپ‌شده و درون‌پردازه‌ای برای ساخت endpoint پویا نیز در اختیار tunnel پروتکلی سازگار در همان chain می‌گذارد. Socks5Server یکی از مصرف‌کنندگان این قابلیت است. برای یک سرویس عادی SOCKS5 از TcpUdpListener استفاده کنید: child داخلی TcpListener، ‏control connection الزامی TCP را می‌پذیرد و child داخلی UdpListener این provider را واگذار می‌کند. یک UdpListener مستقل به‌تنهایی ورودی کامل SOCKS5 نیست، زیرا نمی‌تواند آن control connection نوع TCP را بپذیرد.

این یک capability از interface.h است، نه یک setting در JSON و نه API پیام خارجی instance/api.c. node Socks5Server آن را به‌صورت خودکار از مسیر قبلی نهایی‌شدهٔ خود پیدا می‌کند؛ اپراتور نام node provider را config نمی‌کند.

provider روی Event worker درخواست‌کننده یک socket UDP اختصاصی را با bind انحصاری روی port: 0 می‌سازد و یک handle مقداری و opaque (شامل WID مالک و generation)، آدرس محلی تخصیص‌داده‌شده توسط kernel، و port واقعی را برمی‌گرداند. Handle فقط روی worker مالک معتبر است؛ close تکراری یا handle قدیمی بی‌خطر است و endpoint جدیدتر را نمی‌بندد.

ورود داده از endpoint پویا مسیر عادی chain را طی می‌کند:

dynamic UDP socket -> UdpListener -> TcpUdpListener (در صورت وجود) -> next chain

پاسخ‌های downstream از همان endpoint به peer پین‌شده بازمی‌گردند. socket پویا از address، ‏interface، ‏fwmark، ‏buffer‌های send/receive، ‏whitelist و blacklist listener استفاده می‌کند، اما از port ثابت، بازهٔ port، ‏balance group و multiport backend استفاده نمی‌کند.

پیش از آن‌که یک datagram پویا بتواند line متعلق به provider را بسازد یا استفاده کند، IP نرمال‌شدهٔ peer باید دقیقاً با IP مورد انتظار endpoint برابر باشد. source port غیرصفرِ تنظیم‌شده نیز باید دقیقاً برابر باشد؛ source port صفر نخستین sender معتبر را pin می‌کند و همهٔ port‌های بعدی باید با همان pin برابر باشند. datagram‌های نامطابق بی‌صدا drop می‌شوند و TCP control association را نمی‌بندند یا به آن پیام نمی‌دهند.

در Quiesce، بازکردن یا فعال‌سازی endpoint تازه رد می‌شود. روی worker مالک، ابتدا callback‌های WIO جدا می‌شوند، سپس endpoint از registry محلی worker حذف می‌شود و یک مسیر close واحد و idempotent، line عادی متعلق به provider را finish و نابود می‌کند. این inventory از جدول عادی peer‌ها در socket manager جدا است.

نکات و محدودیت‌ها​

  • UdpListener یک chain Head برای UDP است و به next نیاز دارد.
  • array‌های port فهرست صریح‌اند، نه بازه.
  • برای بازه‌های پیوسته از port-range استفاده کنید.
  • whitelist و blacklist، ‏string‌های CIDR مربوط به IPv4 و IPv6 را می‌پذیرند.
  • fwmark و bindشدن به device به platform وابسته‌اند.
  • line‌های peer در حالت Pause، ‏datagram‌های ورودی را به‌جای bufferکردن دور می‌ریزند.
  • endpoint‌های پویا plumbing داخلی capability هستند، نه port عمومی اضافه که با port یا port-range تنظیم شود.
  • برای سرویس داخلی SOCKS5 UDP، توپولوژی TcpUdpListener -> ... -> Socks5Server را config کنید، نه یک ورودی مستقل UdpListener یا TcpListener.
  • برای رفتار stateless و در سطح packet در UDP، شاید UdpStatelessSocket انتخاب مناسب‌تری از UdpListener باشد.

متادیتای node​

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

ویژگیمقدار
node flagskNodeFlagChainHead | kNodeFlagSupportsSplice
can_have_prevfalse
can_have_nexttrue
layer_groupkNodeLayerAnything
layer_group_prev_nodekNodeLayerNone
layer_group_next_nodekNodeLayerAnything
required_padding_left0 بایت

ارسال UDP با splice​

دریافت UDP همچنان از buffer عادی استفاده می‌کند. در chain واجد شرایط روی Linux، ارسال UDP دادهٔ عادی یا prefix واقعی همراه با بدنهٔ pipe خصوصی را به‌صورت یک datagram به مقصد مشخص می‌پذیرد؛ datagram خالی نیز معتبر است. چیدمان‌های پشتیبانی‌شده از splice بین pipe و socket استفاده می‌کنند. برای pipe با fragment‌های زیاد، یا وقتی پیش از ارسال نتوان حد امن fragment‌ها را اثبات کرد، کل datagram پیش از شروع تجمیع روی socket به buffer عادی تبدیل می‌شود. دادهٔ بیش‌ازحد بزرگ بدون تقسیم رد می‌شود.

فشار پیش از شروع تجمیع همان سیاست drop فعلی را حفظ می‌کند و صف retry برای UDP ایجاد نمی‌شود. splice ناقص یا شکست commit پس از priming فقط socket مربوط را بازنشسته می‌کند و line‌های عادی وابسته از مسیر مالکشان بسته می‌شوند. بنابراین peer‌های یک socket مشترک ممکن است با هم بسته شوند. باقی‌ماندهٔ ناقص دوباره ارسال نمی‌شود و socket‌های دیگر فعال می‌مانند. سیاست خطای ارسال عادی تغییر نمی‌کند.

این پرچم بررسی پشتیبانی پلتفرم، misc.splice، ممنوعیت chain packet و پشتیبانی همهٔ node‌های chain نهایی را دور نمی‌زند. zero-copy سرتاسری یا بهبود کاراییِ اندازه‌گیری‌شده تضمین نمی‌شود.

بازنشستگی listener ثابت ابتدا پذیرش را می‌بندد و سپس worker‌های مالک peer را مطلع می‌کند. اگر اعلان لغو شود، مهلت محدود idle موجود و drain زمان shutdown مسئول بستن باقی می‌مانند؛ ترافیک socket بازنشسته نمی‌تواند peer بسازد یا مهلت آن را تمدید کند. endpoint پویا، handle و WIO و line خود را با هم جدا می‌کند.