TcpUdpConnector
TcpUdpConnector یک wrapper در انتهای chain است که TcpConnector و UdpConnector را در یک node ترکیب میکند. با تنظیمات یکسان، یک connector داخلی برای TCP و یکی برای UDP میسازد و سپس بر اساس metadata مسیریابی WaterWall، connector مناسب هر line را انتخاب میکند.
این node TCP را داخل UDP کپسوله نمیکند و UDP را هم از TCP عبور نمیدهد؛ فقط یک wrapper دوپروتکلی برای connectorهاست.
جایگاه رایج
وقتی node خروجی باید هم TCP و هم UDP را پشتیبانی کند، از TcpUdpConnector استفاده کنید:
Socks5Server -> TcpUdpConnector
TcpUdpListener -> TcpUdpConnector
TcpUdpListener -> SniffRouter -> TcpUdpConnector
PacketsToConnection -> TcpUdpConnector
node قبلی باید اطلاعات protocol را در destination context یا source context قرار دهد. TcpUdpConnector با همین metadata تصمیم میگیرد line را به TcpConnector داخلی بسپارد یا به UdpConnector داخلی.
نمونه ساده
{
"name": "mixed-out",
"type": "TcpUdpConnector",
"settings": {
"address": "198.51.100.10",
"port": 443,
"domain-strategy": "prefer-ipv4",
"nodelay": true,
"fastopen": false,
"large-send-buffer": true,
"large-recv-buffer": true,
"interface": "eth0",
"fwmark": 10,
"balance-mode": "connection"
}
}
هم lineهای TCP و هم lineهای مربوط به peerهای UDP از destination تنظیمشده استفاده میکنند. TcpConnector داخلی lineهای TCP را مدیریت میکند و lineهای UDP به UdpConnector داخلی سپرده میشوند.
نمونه dynamic destination
{
"name": "mixed-out",
"type": "TcpUdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port",
"domain-strategy": "prefer-ipv4",
"large-send-buffer": true,
"large-recv-buffer": true
}
}
این حالت بعد از nodeهایی مثل Socks5Server و SniffRouter مفید است؛ یعنی nodeهایی که address، port و protocol مقصد را در routing context قرار میدهند.
نمونه weighted destination
{
"name": "mixed-out",
"type": "TcpUdpConnector",
"settings": {
"addresses": [
{
"address": "198.51.100.10",
"port": 443,
"weight": 3
},
{
"address": "198.51.100.20",
"port": 443,
"weight": 1
}
],
"domain-strategy": "prefer-ipv4",
"large-send-buffer": true,
"large-recv-buffer": true,
"balance-mode": "connection"
}
}
آرایه addresses بدون تغییر به هر دو connector داخلی داده میشود. اگر امکان انتخاب هر دو protocol وجود دارد، syntax مقصد باید هم برای TcpConnector و هم برای UdpConnector معتبر باشد.
فیلدهای ضروری
فیلدهای سطح بالا:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه node. باید داخل فایل config یکتا باشد. |
type | string | باید دقیقاً "TcpUdpConnector" باشد. |
settings | object | تنظیمات مشترک هر دو connector داخلی؛ باید objectی غیرخالی باشد. |
TcpUdpConnector یک chain end است. نباید next داشته باشد.
تنظیمات destination لازم:
| سبک | فیلدهای لازم | توضیح |
|---|---|---|
| مقصد تکی | address و port | هر connector داخلی که انتخاب شود از همین مقصد استفاده میکند. |
| مقصدهای weighted | addresses | مقصد هر line از یک فهرست وزندار انتخاب میشود. connectorهای داخلی spelling قدیمی adresses را هم میپذیرند، اما در configهای جدید از addresses استفاده کنید. |
addresses را با address یا port top-level ترکیب نکنید.
تنظیمات مشترک connector
object مربوط به settings باید هنگام راهاندازی برای هر دو connector داخلی معتبر باشد. حتی اگر قرار است در runtime فقط از یک protocol استفاده کنید، این نکته همچنان مهم است؛ چون wrapper موقع خواندن config هر دو connector را میسازد.
| گزینه | روی کدام child اثر دارد | توضیح |
|---|---|---|
address | TCP و UDP | آدرس ثابت، domain، "src_context->address" یا "dest_context->address". |
port | TCP و UDP | پورت ثابت، "src_context->port" یا "dest_context->port". |
addresses | TCP و UDP | لیست weighted destination. |
domain-strategy | TCP و UDP | استراتژی انتخاب نتیجه DNS برای مقصدهای domain. |
large-send-buffer | TCP و UDP | تنظیم SO_SNDBUF در صورت پشتیبانی. |
large-recv-buffer | TCP و UDP | تنظیم SO_RCVBUF در صورت پشتیبانی. |
fwmark | TCP و UDP | اعمال socket mark به سبک Linux در صورت پشتیبانی. |
interface | TCP و UDP | محدود کردن socketهای خروجی به یک network device در صورت پشتیبانی. |
source-ip | TCP و UDP | bind کردن socketهای خروجی به یک source IP محلی مشخص. |
nodelay | TCP child | فعالسازی TCP_NODELAY روی TCP socketهای خروجی. |
fastopen | TCP child | درخواست TCP_FASTOPEN در صورت پشتیبانی. |
balance-mode | UDP child | انتخاب weighted destination در UDP: "connection" یا "packet". |
برای جزئیات انتخاب destination، DNS، گزینههای socket و balance mode در UDP، به صفحههای TcpConnector و UdpConnector مراجعه کنید.
UdpConnector از syntax مخصوص UDP مثل "random(x,y)" برای port پشتیبانی میکند، اما TcpConnector نه. چون TcpUdpConnector هر دو child connector را میسازد، از syntax مخصوص UDP اینجا خودداری کنید مگر اینکه در کد فعلی برای TcpConnector هم معتبر شده باشد.
انتخاب protocol
با دریافت upstream Init، TcpUdpConnector یکی از connectorهای داخلی را انتخاب و آن را در per-line state ذخیره میکند. callbackهای بعدی upstream هم به همان connector میروند؛ بنابراین حتی اگر node دیگری بعداً routing context را تغییر دهد، انتخاب اولیه عوض نمیشود.
ترتیب انتخاب:
| مرحله | رفتار |
|---|---|
| 1 | line->routing_context.dest_ctx بررسی میشود. |
| 2 | اگر destination context دقیقاً flag مربوط به TCP را داشته باشد، TcpConnector داخلی انتخاب میشود. |
| 3 | اگر destination context دقیقاً flag مربوط به UDP را داشته باشد، UdpConnector داخلی انتخاب میشود. |
| 4 | اگر destination context هیچ protocol flag نداشته باشد، به line->routing_context.src_ctx برمیگردد. |
| 5 | اگر source context دقیقاً flag مربوط به TCP یا UDP را داشته باشد، connector متناظر انتخاب میشود. |
| 6 | اگر protocol مشخصی پیدا نشود، برنامه آن را خطایی fatal در config یا routing در نظر میگیرد. |
protocol مقصد بر protocol مبدأ اولویت دارد. به این ترتیب، nodeهای آگاه از مسیریابی میتوانند ترافیک inbound را با یک transport بگیرند و transport خروجی را بر اساس مقصد درخواستی انتخاب کنند.
جریان runtime
هنگام راهاندازی، TcpUdpConnector دو tunnel داخلی میسازد:
TcpConnectorبا نامی شبیه<node-name>.tcp-connectorUdpConnectorبا نامی شبیه<node-name>.udp-connector
هر دو node داخلی زیر wrapper در chain قرار میگیرند. callbackها در runtime این مسیر را طی میکنند:
- node قبلی upstream
Initرا بهTcpUdpConnectorمیفرستد - wrapper یکی از connectorهای داخلی را انتخاب و مقداردهی اولیه میکند
- callbackهای upstream مربوط به payload، pause، resume، establishment و finish به همان connector فرستاده میشوند
- callbackهای downstream آن connector به
TcpUdpConnectorبرمیگردند - wrapper آنها را در جهت downstream به node قبلی میفرستد
مالکیت line
TcpUdpConnector هیچ connection lineای نمیسازد و از بین نمیبرد. مالک line همان adapter یا bridge قبلی است که آن را ساخته است.
این wrapper فقط connector انتخابشده را در یک per-line state کوچک نگه میدارد و پیش از فرستادن Finish واقعی در هر جهت، آن state را پاک میکند. lineDestroy() هیچوقت از این node فراخوانی نمیشود.
اشتباههای رایج
- از
TcpUdpConnectorبهعنوان protocol converter استفاده نکنید؛ کار آن فقط انتخاب بین connector داخلی TCP و UDP است. - برای آن
nextقرار ندهید؛ chain end است. - نمیتوانید برای TCP و UDP دو destination جدا تنظیم کنید؛
settingsمیان هر دو مشترک است. - در configهایی که internal
TcpConnectorهم باید ساخته شود از syntax مخصوص UDP مثل"random(x,y)"استفاده نکنید. - آن را بعد از nodeای نگذارید که protocol دقیق TCP یا UDP را در metadata مشخص نمیکند.
متادیتای node
متادیتای برگرفته از کد منبع:
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagChainEnd | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | false |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayerNone |
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 سرتاسری یا بهبود کاراییِ اندازهگیریشده تضمین نمیشود.
هر دو connector داخلی پرچم قابلیت خود را حفظ میکنند. بررسی واجد شرایط بودن، حتی برای line مربوط به TCP، هر دو شاخه و helperهای درجشده را شامل میشود. انتخاب پروتکل و مسیریابی peer در UDP تغییر نمیکند.