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

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 شوند.

فیلدهای ضروری​

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

فیلدنوعتوضیح
namestringنام دلخواه node. باید داخل فایل config یکتا باشد.
typestringباید دقیقاً "TcpUdpListener" باشد.
settingsobjectتنظیمات مشترک هر دو listener داخلی؛ باید objectی غیرخالی باشد.
nextstringnode‌ای که هم TCP line‌ها و هم UDP peer line‌ها را دریافت می‌کند.

فیلدهای ضروری داخل settings:

فیلدنوعتوضیح
addressstringآدرس 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 اثر داردتوضیح
nodelayTCP childفعال‌سازی TCP_NODELAY روی socket‌های TCP پذیرفته‌شده.
large-send-bufferTCP و UDP childrenتنظیم SO_SNDBUF در صورت پشتیبانی.
large-recv-bufferTCP و UDP childrenتنظیم SO_RCVBUF در صورت پشتیبانی.
interfaceTCP و UDP childrenمحدود کردن socket‌ها به یک interface محلی در صورت پشتیبانی.
fwmarkTCP و UDP childrenاعمال socket mark به سبک Linux در صورت پشتیبانی.
balance-groupTCP و UDP childrenlistener‌های سازگار و هم‌پورت را برای توزیع sticky در یک گروه قرار می‌دهد.
balance-intervalTCP و UDP childrenمدت حفظ شدن client یا peer تکراری روی یک عضو از balance group.
initial-idle-timeout-msTCP childtimeout اولیه idle قبل از فعالیت payload TCP سمت listener.
active-idle-timeout-msTCP childtimeout idle بعد از شروع فعالیت TCP.
multiport-backendTCP و UDP childrenbackend برای port-range پیوسته: "iptables" یا "socket".
whitelistTCP و UDP childrenIP‌ها و محدوده‌های CIDR مجاز برای client یا peer.
blacklistTCP و UDP childrenIP‌ها و محدوده‌های CIDR مسدود برای client یا peer.

برای جزئیات گزینه‌های socket، پیش‌فرض timeout‌ها و تنظیمات multiport، به صفحه‌های TcpListener و UdpListener مراجعه کنید.

جریان runtime​

هنگام راه‌اندازی، TcpUdpListener دو tunnel داخلی می‌سازد:

  • TcpListener با نامی شبیه <node-name>.tcp-listener
  • UdpListener با نامی شبیه <node-name>.udp-listener

هر دو node داخلی در chain قرار می‌گیرند و مالک socket‌های واقعی و lifecycle مربوط به line‌های خود هستند:

  • TcpListener داخلی socket‌های TCP را می‌پذیرد و line‌های TCP را می‌سازد
  • UdpListener داخلی برای peer‌های UDP line می‌سازد
  • callback‌های upstream هر دو node داخلی وارد TcpUdpListener می‌شوند
  • TcpUdpListener callback‌های upstream شامل Init، Payload، Pause، Resume، Est و Finish را به next می‌فرستد

در مسیر downstream، callback node بعدی به TcpUdpListener می‌رسد. wrapper سپس source protocol ثبت‌شده روی line را بررسی می‌کند:

metadata protocol sourcechild downstream
TCPchild داخلی TcpListener
UDPchild داخلی 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 جایگزین نکنید؛ TcpListener provider پویای UDP ندارد.
  • نمی‌توان برای TCP و UDP دو مجموعه port متفاوت روی این wrapper تعریف کرد؛ هر دو listener همان settings را دریافت می‌کنند.
  • metadata مربوط به source protocol را پیش از بازگشت callback‌های downstream به این node حذف یا خراب نکنید.

متادیتای node​

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

ویژگیمقدار
node flagskNodeFlagChainHead | kNodeFlagSupportsSplice
can_have_prevfalse
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayerNone
layer_group_next_nodekNodeLayer4
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 داخلی پرچم قابلیت خود را حفظ می‌کنند. مسیر TCP فقط وقتی کل chain گسترش‌یافته واجد شرایط باشد از مسیر فعلی splice برای دریافت و ارسال استفاده می‌کند؛ دریافت UDP عادی می‌ماند. انتخاب پروتکل تغییر نمی‌کند.