TcpUdpListener
TcpUdpListener یک wrapper در ابتدای chain است که TcpListener و UdpListener را در یک node ترکیب میکند. با تنظیمات یکسان، یک listener داخلی برای TCP و یکی برای UDP میسازد تا یک ورودی WaterWall بتواند هر دو نوع ترافیک را روی آدرس و مجموعه پورتهای یکسان بپذیرد.
این node TCP را به UDP یا UDP را به TCP تبدیل نمیکند؛ فقط ترافیک ورودی هر دو adapter را به یک node بعدی مشترک میفرستد.
جایگاه رایج
وقتی یک ورودی باید هم TCP و هم UDP را بپذیرد، از TcpUdpListener استفاده کنید:
TcpUdpListener -> SpeedTestServer
TcpUdpListener -> SniffRouter -> TcpUdpConnector
TcpUdpListener -> Router -> ...
TcpUdpListener -> [optional middle tunnels] -> Socks5Server -> TcpUdpConnector
node بعدی باید توان کار با ترافیک ورودی را داشته باشد. اگر در محیط production هر دو نوع TCP و UDP وارد میشوند، node بعدی باید هم lineهای معمولی TCP و هم lineهای peer در UDP را پشتیبانی کند، یا آنها را بر اساس protocol مسیریابی کند.
TcpUdpListener ورودی استاندارد داخلی برای
Socks5Server با udp: true است: child نوع TCP، control
connection مربوط به SOCKS5 را میپذیرد و child نوع UDP، provider مربوط به endpoint
پویا را فراهم میکند. یک TcpListener ساده فقط SOCKS5 TCP/CONNECT را پشتیبانی
میکند و نمیتواند نیاز UDP ASSOCIATE را برآورده کند.
نمونه ساده
[
{
"name": "mixed-in",
"type": "TcpUdpListener",
"settings": {
"address": "0.0.0.0",
"port": 443,
"nodelay": true,
"large-send-buffer": true,
"large-recv-buffer": true
},
"next": "mixed-out"
},
{
"name": "mixed-out",
"type": "TcpUdpConnector",
"settings": {
"address": "127.0.0.1",
"port": 8443,
"reuseaddr": true,
"nodelay": true
}
}
]
در این مثال، TcpListener داخلی ترافیک TCP پورت 443 را میفرستد و UdpListener داخلی ترافیک UDP همان پورت را. TcpUdpConnector در انتهای chain با توجه به protocol ثبتشده در metadata هر line، خروجی TCP یا UDP را انتخاب میکند.
نمونه multiport
{
"name": "mixed-public-entry",
"type": "TcpUdpListener",
"settings": {
"address": "0.0.0.0",
"port": [
443,
8443
],
"nodelay": true,
"large-send-buffer": true,
"large-recv-buffer": true,
"interface": "eth0",
"fwmark": 10,
"balance-group": "public-mixed",
"balance-interval": 30000,
"multiport-backend": "socket",
"whitelist": [
"192.0.2.0/24",
"2001:db8::/64"
],
"blacklist": [
"192.0.2.99/32"
]
},
"next": "next-node"
}
همین فهرست port برای TCP و UDP به کار میرود. پورت TCP شماره 443 و پورت UDP شماره 443 دو socket جدا هستند و میتوانند همزمان bind شوند.
فیلدهای ضروری
فیلدهای سطح بالا:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه node. باید داخل فایل config یکتا باشد. |
type | string | باید دقیقاً "TcpUdpListener" باشد. |
settings | object | تنظیمات مشترک هر دو listener داخلی؛ باید objectی غیرخالی باشد. |
next | string | nodeای که هم TCP lineها و هم UDP peer lineها را دریافت میکند. |
فیلدهای ضروری داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
address | string | آدرس bind محلی برای هر دو child listener. |
port یا port-range | عدد، آرایه، یا آرایه بازه | portهایی که هر دو listener TCP و UDP استفاده میکنند. باید دقیقاً یکی از این دو فیلد را بنویسید. |
تنظیم پورت
syntax تعریف پورت در TcpUdpListener همان syntax مورد استفاده TcpListener و UdpListener است.
یک پورت
{
"settings": {
"address": "0.0.0.0",
"port": 443
}
}
لیست پورت مشخص
{
"settings": {
"address": "0.0.0.0",
"port": [
443,
8443,
2083
]
}
}
این حالت فقط روی پورتهای لیستشده برای هر دو TCP و UDP گوش میدهد.
بازه پیوسته
{
"settings": {
"address": "0.0.0.0",
"port-range": [
40000,
40100
]
}
}
port-range باید یک آرایه دو عضوی باشد: [min, max].
تنظیمات اختیاری
چون object یکسان settings به هر دو listener داخلی داده میشود، بیشتر گزینهها همانهایی هستند که در صفحههای TcpListener و UdpListener توضیح داده شدهاند.
| گزینه | روی کدام child اثر دارد | توضیح |
|---|---|---|
nodelay | TCP child | فعالسازی TCP_NODELAY روی socketهای TCP پذیرفتهشده. |
large-send-buffer | TCP و UDP children | تنظیم SO_SNDBUF در صورت پشتیبانی. |
large-recv-buffer | TCP و UDP children | تنظیم SO_RCVBUF در صورت پشتیبانی. |
interface | TCP و UDP children | محدود کردن socketها به یک interface محلی در صورت پشتیبانی. |
fwmark | TCP و UDP children | اعمال socket mark به سبک Linux در صورت پشتیبانی. |
balance-group | TCP و UDP children | listenerهای سازگار و همپورت را برای توزیع sticky در یک گروه قرار میدهد. |
balance-interval | TCP و UDP children | مدت حفظ شدن client یا peer تکراری روی یک عضو از balance group. |
initial-idle-timeout-ms | TCP child | timeout اولیه idle قبل از فعالیت payload TCP سمت listener. |
active-idle-timeout-ms | TCP child | timeout idle بعد از شروع فعالیت TCP. |
multiport-backend | TCP و UDP children | backend برای port-range پیوسته: "iptables" یا "socket". |
whitelist | TCP و UDP children | IPها و محدودههای CIDR مجاز برای client یا peer. |
blacklist | TCP و UDP children | IPها و محدودههای CIDR مسدود برای client یا peer. |
برای جزئیات گزینههای socket، پیشفرض timeoutها و تنظیمات multiport، به صفحههای TcpListener و UdpListener مراجعه کنید.
جریان runtime
هنگام راهاندازی، TcpUdpListener دو tunnel داخلی میسازد:
TcpListenerبا نامی شبیه<node-name>.tcp-listenerUdpListenerبا نامی شبیه<node-name>.udp-listener
هر دو node داخلی در chain قرار میگیرند و مالک socketهای واقعی و lifecycle مربوط به lineهای خود هستند:
TcpListenerداخلی socketهای TCP را میپذیرد و lineهای TCP را میسازدUdpListenerداخلی برای peerهای UDP line میسازد- callbackهای upstream هر دو node داخلی وارد
TcpUdpListenerمیشوند TcpUdpListenercallbackهای upstream شاملInit،Payload،Pause،Resume،EstوFinishرا بهnextمیفرستد
در مسیر downstream، callback node بعدی به TcpUdpListener میرسد. wrapper سپس source protocol ثبتشده روی line را بررسی میکند:
| metadata protocol source | child downstream |
|---|---|
| TCP | child داخلی TcpListener |
| UDP | child داخلی UdpListener |
| protocol نامشخص یا flag مبهم TCP/UDP | خطای fatal در config یا routing |
به همین دلیل، nodeهای بعد از TcpUdpListener باید metadata مربوط به source protocol را روی line حفظ کنند.
مالکیت line
TcpUdpListener خودش per-line state ندارد، line نمیسازد و lineDestroy() را هم فراخوانی نمیکند.
مالکیت هر line در اختیار همان adapter داخلی میماند که آن را ساخته است:
- TCP lineها متعلق به child داخلی
TcpListenerهستند - UDP peer lineها متعلق به child داخلی
UdpListenerهستند
واگذاری endpoint پویای UDP
برای یک tunnel پروتکلی سازگار در ادامهٔ همان chain، از جمله Socks5Server،
TcpUdpListener قابلیت تایپشدهٔ endpoint پویای UDP را به child داخلی
UdpListener واگذار میکند. Socks5Server این provider را
بهصورت خودکار از مسیر قبلی نهاییشدهٔ خود پیدا میکند؛ هیچ نام node provider یا
setting مربوط به JSON API لازم نیست. مالک socket پویا، registry محلی worker و هر
line عادی ساختهشده توسط provider همچنان خود child است؛ wrapper فقط chain عادی
callback را حفظ میکند.
dynamic UDP child endpoint -> UdpListener child -> TcpUdpListener -> next chain
در مسیر بازگشت، wrapper با protocol منبع line، child UDP را انتخاب میکند تا پاسخ downstream به endpoint اختصاصی برسد. Wrapper نباید lifecycle endpoint child را جداگانه close، drain یا دوباره اجرا کند. تخصیص پویا از یک port موقت UDP در child استفاده میکند، نه port ثابت TCP/UDP تنظیمشده روی wrapper.
Child پیش از ساخت line، IP نرمالشدهٔ مورد انتظار endpoint را بررسی میکند؛
source port غیرصفر باید دقیقاً برابر باشد و source port صفر نخستین packet معتبر را
pin میکند. موارد نامطابق بیصدا drop میشوند و TCP control line مرتبط را
نمیبندند یا به آن پیام نمیدهند. تنظیم large-*-buffer: false نیز برای socket
پویای child مقدار پیشفرض Kernel را دستنخورده میگذارد.
اشتباههای رایج
- از
TcpUdpListenerبهعنوان protocol converter استفاده نکنید؛ هر دو protocol را میپذیرد، اما یکی را به دیگری تبدیل نمیکند. - آن را به node بعدی که فقط TCP را میفهمد وصل نکنید اگر ترافیک UDP هم ممکن است بیاید.
- در توپولوژی
Socks5Serverکه UDP را فعال میکند آن را باTcpListenerجایگزین نکنید؛TcpListenerprovider پویای UDP ندارد. - نمیتوان برای TCP و UDP دو مجموعه port متفاوت روی این wrapper تعریف کرد؛ هر دو listener همان
settingsرا دریافت میکنند. - metadata مربوط به source protocol را پیش از بازگشت callbackهای downstream به این node حذف یا خراب نکنید.
متادیتای node
متادیتای برگرفته از کد منبع:
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagChainHead | kNodeFlagSupportsSplice |
can_have_prev | false |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayerNone |
layer_group_next_node | kNodeLayer4 |
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 داخلی پرچم قابلیت خود را حفظ میکنند. مسیر TCP فقط وقتی کل chain گسترشیافته واجد شرایط باشد از مسیر فعلی splice برای دریافت و ارسال استفاده میکند؛ دریافت UDP عادی میماند. انتخاب پروتکل تغییر نمیکند.