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"
}
فیلدهای الزامی
فیلدهای سطح اول:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نامی که کاربر برای node انتخاب میکند و باید در فایل config یکتا باشد. |
type | string | باید دقیقاً "UdpListener" باشد. |
next | string | نام nodeای که lineها و payloadهای peerهای UDP را دریافت میکند. |
فیلدهای الزامی در settings:
| فیلد | نوع | توضیح |
|---|---|---|
address | string | آدرس محلی برای bind، مانند "0.0.0.0"، "::" یا یک IP محلی مشخص. |
port یا port-range | number، 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-buffer | boolean یا integer مثبت | true | مقدار SO_SNDBUF را روی socketهای listener نوع UDP تنظیم میکند. |
large-recv-buffer | boolean یا integer مثبت | true | مقدار SO_RCVBUF را روی socketهای listener نوع UDP تنظیم میکند. |
interface | string | تنظیم نشده | listener را به یک interface محلی شبکه محدود میکند. |
fwmark | integer | تنظیم نشده | در صورت پشتیبانی، یک socket mark به سبک Linux اعمال میکند. |
balance-group | string | تنظیم نشده | listenerهای سازگار و همپورت را برای توزیع پایدار peerها در یک گروه قرار میدهد. |
balance-interval | integer، بر حسب میلیثانیه | مقدار پیشفرض socket manager | مدت حفظ انتخاب قبلی برای peerهای تکراری در balance group. |
multiport-backend | string | مقدار پیشفرض runtime برای port-range | backend مربوط به بازهی پیوستهی پورتها: "iptables" یا "socket". |
whitelist | arrayای از stringها | تنظیم نشده | IPها یا بازههای CIDR مجاز برای peer. |
blacklist | arrayای از 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 آنها با فهرست منطبق باشد. |
blacklist | peerهایی را که 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:
- با نخستین datagram پذیرفتهشده، یک line تازه در WaterWall ساخته میشود.
- آدرس peer در line state مربوط به listener ذخیره میشود.
- datagramهای بعدی همان peer تا زمان انقضای line از آن استفاده میکنند.
- 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 flags | kNodeFlagChainHead | kNodeFlagSupportsSplice |
can_have_prev | false |
can_have_next | true |
layer_group | kNodeLayerAnything |
layer_group_prev_node | kNodeLayerNone |
layer_group_next_node | kNodeLayerAnything |
required_padding_left | 0 بایت |
ارسال 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 خود را با هم جدا میکند.