UdpConnector
UdpConnector adapter خروجی UDP است. socketهای UDP محلی worker را مدیریت میکند، address و port مقصد را انتخاب میکند و datagramها را میان node قبلی و peer راه دور جابهجا میسازد.
جایگاه رایج:
UdpListener -> ... -> UdpConnector -> سرویس UDP راه دور
این node معمولاً انتهای یک UDP stream-style chain است.
UdpConnector فقط normal lineهای لایه ۴ را میپذیرد. worker packet line لایه ۳
باید پیش از رسیدن به این node از bridge صریح PacketsToStream عبور کند. اگر payload
در سمت دریافت باید دوباره وارد packet chain شود، از StreamToPackets استفاده
کنید. برای endpoint مستقیم UDP روی packet line، adapter سازگار با packet مانند
UdpStatelessSocket مناسبتر است.
قابلیتها
- مدیریت socketهای UDP خروجی در state محلی هر worker.
- انتخاب address و port مقصد از تنظیمات ثابت، مقصدهای وزندار یا routing context.
- resolve کردن نام domain از طریق یک
DomainResolverداخلی و async. - ارسال payloadهای upstream از node قبلی به remote UDP peer.
- ارسال datagramهای UDP دریافتشده بهصورت downstream به node قبلی.
- هدایت datagramهای دریافتی به line مبدأ و رد کردن peerهای ثبتنشده.
- ردیابی UDP lineها با idle timeout.
- اعمال socket optionهای اختیاری مثل socket bufferها،
SO_MARK، device binding و source-IP binding در صورت پشتیبانی.
UdpConnector در انتهای chain قرار میگیرد، با upstream Init راه میافتد و به node next نیاز ندارد.
نمونه تنظیم
{
"name": "udp-out",
"type": "UdpConnector",
"settings": {
"address": "example.com",
"port": "random(40000,40100)",
"large-send-buffer": true,
"large-recv-buffer": true,
"fwmark": 10,
"interface": "eth0",
"source-ip": "192.0.2.10",
"domain-strategy": "prefer-ipv4"
}
}
نمونه چند مقصد وزندار
{
"name": "udp-out",
"type": "UdpConnector",
"settings": {
"balance-mode": "packet",
"addresses": [
{
"address": "1.1.1.1",
"port": 53,
"weight": 3
},
{
"address": "8.8.8.8",
"port": "random(40000,40100)",
"weight": 1
}
],
"large-send-buffer": true,
"large-recv-buffer": true,
"domain-strategy": "prefer-ipv4"
}
}
balance-mode مستقیماً داخل settings قرار میگیرد، نه داخل هر object در addresses.
فیلدهای ضروری
فیلدهای سطح بالا:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه node. باید داخل فایل config یکتا باشد. |
type | string | باید دقیقاً "UdpConnector" باشد. |
settings | object | تنظیمات مقصد و socket. |
settings باید دقیقاً از یک سبک مقصد استفاده کند:
| سبک | فیلدهای لازم | توضیح |
|---|---|---|
| مقصد تکی | address و port | هر line از همان قانون مقصد استفاده میکند. |
| مقصدهای وزندار | addresses | بسته به balance-mode برای هر line یا packet یک مقصد بر اساس وزن انتخاب میشود. |
addresses را با address یا port top-level ترکیب نکنید.
انتخاب مقصد
مقصد تکی
از address و port مستقیماً زیر settings استفاده کنید.
{
"settings": {
"address": "example.com",
"port": 53
}
}
مقادیر معتبر برای address:
| مقدار | رفتار |
|---|---|
| string IPv4 | ارسال به آن آدرس IPv4. |
| string IPv6 | ارسال به آن آدرس IPv6. |
| string domain | resolve کردن domain پیش از ساخت socket. |
"src_context->address" | استفاده از آدرس source در routing context مربوط به line. |
"dest_context->address" | استفاده از آدرس destination در routing context مربوط به line. |
نام domain ثابت باید بین ۱ تا ۲۵۵ بایت باشد.
مقادیر معتبر برای port:
| مقدار | رفتار |
|---|---|
| عدد | استفاده از آن پورت ثابت. باید 1 تا 65535 باشد. |
| string عددی | استفاده از آن پورت ثابت، مثلاً "53". |
"src_context->port" | استفاده از پورت source در routing context مربوط به line. |
"dest_context->port" | استفاده از پورت destination در routing context مربوط به line. |
"random(x,y)" | انتخاب یک پورت تصادفی در بازه inclusive [x, y]. |
در random(x,y) هر دو کران باید در بازهٔ 1..65535 باشند و x <= y برقرار باشد.
مقدار کامل کرانها استفاده میشود؛ برای مثال random(1000,2000) از بازهٔ 1000..2000
انتخاب میکند. متن اضافی پس از ) رد میشود.
مقصدهای weighted
برای انتخاب مقصد وزندار از addresses استفاده کنید.
{
"settings": {
"addresses": [
{
"address": "1.1.1.1",
"port": 53,
"weight": 3
},
{
"address": "8.8.8.8",
"port": 53,
"weight": 1
}
]
}
}
هر object باید شامل باشد:
| فیلد | نوع | توضیح |
|---|---|---|
address | string | همان فرمهای معتبر address در مقصد تکی. |
port | عدد یا string | همان فرمهای معتبر port در مقصد تکی، شامل random(x,y). |
weight | عدد صحیح مثبت | وزن نسبی این مقصد هنگام انتخاب. |
برخلاف TcpConnector، objectهای مقصد در UdpConnector نمیتوانند گزینههای socket را جداگانه تغییر دهند. گزینههایی مانند large-send-buffer، interface، source-ip و domain-strategy باید در سطح بالای settings قرار بگیرند.
parser برای سازگاری کلید قدیمی و غلطنویسیشده adresses را هم میپذیرد، اما در config جدید از addresses استفاده کنید و هر دو نام را همزمان ننویسید.
balance mode
balance-mode زمان انتخاب مقصد وزندار را مشخص میکند.
| مقدار | رفتار |
|---|---|
"connection" | حالت پیشفرض؛ هنگام upstream Init یک مقصد انتخاب میشود و تمام payloadهای آن line از همان مقصد استفاده میکنند. |
"packet" | پیش از ارسال هر upstream payload، مقصد تازهای بر اساس وزن انتخاب میشود. |
balance-mode اختیاری است و پیشفرض "connection" است.
normal lineها در mode برابر "packet" میتوانند از مقصدهای IPv4 و IPv6 استفاده
کنند. انتخاب socket و source port محلی بهصورت داخلی مدیریت میشود.
تنظیمات اختیاری
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
balance-mode | string | "connection" | حالت انتخاب مقصد weighted: "connection" یا "packet". |
large-send-buffer | boolean یا عدد مثبت | true | تنظیم SO_SNDBUF روی socketهای UDP ساختهشده. |
large-recv-buffer | boolean یا عدد مثبت | true | تنظیم SO_RCVBUF روی socketهای UDP ساختهشده. |
fwmark | integer | تنظیم نشده | اعمال socket mark به سبک Linux از طریق SO_MARK در صورت پشتیبانی. |
interface | string | تنظیم نشده | محدود کردن socketهای خروجی به یک network device محلی در صورت پشتیبانی. |
source-ip | string | تنظیم نشده | socket خروجی را با یک source port موقت به source IP محلی مشخص bind میکند. |
domain-strategy | string یا integer | dns.domain-strategy در core | نحوه انتخاب نتایج DNS برای مقصدهای domain. |
گزینههای socket buffer
large-send-buffer و large-recv-buffer مقادیر زیر را میپذیرند:
| مقدار | معنا |
|---|---|
true | استفاده از buffer بزرگ پیشفرض WaterWall برای socket؛ در حال حاضر 16777216 بایت. |
false | تنظیم نکردن صریح اندازه buffer و استفاده از مقدار پیشفرض kernel. |
| عدد مثبت | درخواست مستقیم آن تعداد بایت. |
هر دو گزینه بهطور پیشفرض buffer بزرگ WaterWall را روی socketهای UDP connector فعال میکنند.
domain strategy
domain-strategy مشخص میکند وقتی DNS هم IPv4 و هم IPv6 برمیگرداند، کدام آدرس برای مقصد domain انتخاب شود.
اگر این گزینه را ننویسید، مقدار dns.domain-strategy در core به کار میرود. در نبود آن هم WaterWall از "prefer-ipv4" استفاده میکند.
مقادیر معتبر string:
| مقدار | رفتار |
|---|---|
"prefer-ipv4" | نخستین نتیجه IPv4 را انتخاب میکند و اگر وجود نداشت به IPv6 برمیگردد. |
"prefer-ipv6" | نخستین نتیجه IPv6 را انتخاب میکند و اگر وجود نداشت به IPv4 برمیگردد. |
"only-ipv4" | فقط نتیجه IPv4 را میپذیرد؛ نبودن IPv4 یعنی resolution برای آن line قابل استفاده نیست. |
"only-ipv6" | فقط نتیجه IPv6 را میپذیرد؛ نبودن IPv6 یعنی resolution برای آن line قابل استفاده نیست. |
"accept-dns-returned-order" | نخستین آدرس قابل استفاده در ترتیب بازگشتی resolver را انتخاب میکند. |
مقادیر integer قدیمی هم پذیرفته میشوند:
| مقدار | استراتژی |
|---|---|
0 | accept-dns-returned-order |
1 | prefer-ipv4 |
2 | prefer-ipv6 |
3 | only-ipv4 |
4 | only-ipv6 |
interface، source IP و egress pinning
interface socket UDP را به یک device محلی محدود میکند.
در Linux، WaterWall در صورت امکان از SO_BINDTODEVICE استفاده میکند. روی platformهای بدون device binding، socket به آدرس IPv4 مربوط به interface bind میشود. اگر آدرسی پیدا نشود، ساخت socket شکست میخورد.
source-ip socket UDP را به یک source IP محلی مشخص با source port 0 bind میکند؛ این از OS یک port موقت میخواهد. خانواده آدرس باید با خانواده مقصد انتخابشده مطابقت داشته باشد.
اگر loop protection در TunDevice یک egress pin خودکار ایجاد کرده باشد و interface را ننوشته باشید، UdpConnector از همان pin استفاده میکند. source-ip بهتنهایی آن interface را تغییر نمیدهد؛ یا source IP را از همان interface انتخاب کنید، یا interface را صریحاً بنویسید.
نمونههای رایج
مقصد DNS ثابت
{
"name": "dns-out",
"type": "UdpConnector",
"settings": {
"address": "1.1.1.1",
"port": 53
}
}
مقصد مبتنی بر context
{
"name": "context-udp-out",
"type": "UdpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}
این حالت پس از nodeهایی کاربرد دارد که routing context را بهصورت پویا پر یا بازنویسی میکنند.
یک tunnel پروتکلی داخلی میتواند مقصد مذاکرهشده را برای یک adapter خروجی
authoritative علامت بزند. در این حالت UdpConnector آدرس و port دادهشده را حفظ
میکند و destination selector یا packet balancing تنظیمشده را اعمال نمیکند. این
metadata داخلی chain است، نه یک setting در JSON؛ برای نمونه relay UDP در SOCKS5
میتواند از BND.ADDR و BND.PORT مذاکرهشده با proxy استفاده کند.
Router یا sniffer قبلی ممکن است metadata مربوط به protocolهای مشاهدهشده را پاک
کند، اما این authority کنترل مسیر از آن reset محدود عبور میکند. UdpConnector
آن را در setup خروجی دقیقاً یکبار مصرف و پیش از تصمیمهای routing عادی بعدی پاک
میکند.
پورت مقصد تصادفی
{
"name": "random-port-out",
"type": "UdpConnector",
"settings": {
"address": "203.0.113.10",
"port": "random(40000,40100)"
}
}
در mode برابر "connection"، پورت تصادفی هنگام مقداردهی line یک بار انتخاب میشود و تا پایان عمر همان line ثابت میماند.
استخر DNS packet-balanced
{
"name": "packet-balanced-dns",
"type": "UdpConnector",
"settings": {
"balance-mode": "packet",
"addresses": [
{
"address": "1.1.1.1",
"port": 53,
"weight": 3
},
{
"address": "8.8.8.8",
"port": 53,
"weight": 1
}
]
}
}
راهاندازی socket و lifecycle
در طول upstream init، UdpConnector:
- آدرس و پورت مقصد را برای line انتخاب میکند.
domain-strategyرا روی destination context line تنظیم میکند.- در صورت نیاز از طریق
DomainResolverداخلی domain را resolve میکند. - resourceهای UDP محلی worker را برای مقصد resolveشده آماده میکند.
- bufferهای send/receive و تنظیمات
interface،fwmark، source IP و egress pin را روی هر socket تازهای که لازم باشد اعمال میکند. - پردازش receive را در صورت نیاز پیش از ارسال downstream
Estآغاز میکند.
UDP در اینجا handshake اتصال ندارد. Est آماده بودن binding محلی line را نشان
میدهد، نه پاسخ دادن peer راه دور.
domain resolution
DNS resolution هنگام init و بهشکل async انجام میشود. payloadهایی که پیش از پایان DNS میرسند در صف محدود resolver میمانند. شکست resolution باعث finish شدن فوری line است.
در balance mode "packet"، domain nameهای داخل objectهای مقصد weighted بهصورت lazy per destination object روی هر WaterWall line resolve میشوند:
- نخستین packetی که یک domain حلنشده را انتخاب کند، یک DNS request از نوع async برای همان object آغاز میکند
- packetهای مربوط به آن مقصد در یک صف محدود منتظر میمانند
- پس از موفقیت resolution، پیش از تخلیه صف resource مقصد آماده میشود؛ شکست آن فقط صف همان مقصد را حذف میکند
- context مقصد ثابت برای packetهای بعدی همان line cache میشود؛ مقصدی که از line context متغیر میآید پیش از استفاده دوباره ساخته میشود
- اگر line context متغیر هنگام pending بودن DNS request قبلی برای همان slot مقصد تغییر کند، packet جدید بهجای پیوستن به request منسوخ drop میشود
- cache زماندار DNS یا DNS request جدا برای هر packet وجود ندارد
مدیریت socket محلی worker
state مربوط به socket محلی worker و وابسته به خانواده آدرس است. پاسخ فقط از endpointهای مورد انتظار پذیرفته میشود و source ناشناس دور ریخته میشود. رفتار socket و source port محلی توسط connector مدیریت میشود و نباید بهعنوان شناسه line استفاده شود. pause و idle deadline به ازای line مستقلاند.
جریان داده
از node قبلی به remote peer:
payload upstream -> UdpConnector -> ارسال UDP
از remote peer به node قبلی:
دریافت UDP -> UdpConnector -> payload downstream
در هر دو balance mode، datagram دریافتی فقط از peer ثبتشده پذیرفته و به line مبدأ هدایت میشود.
کنترل جریان و buffering
در حالی که DNS یا resolution مقصد packet در انتظار است، UdpConnector ممکن است payloadهای upstream را صف کند.
آستانههای صف فعلی:
| وضعیت صف | رفتار |
|---|---|
رسیدن حجم یا هزینهٔ ظرفیت صف به 1 KiB | node قبلی pause میشود. |
| تکمیل writeهای pending | node قبلی resume میشود. |
عبور حجم یا هزینهٔ ظرفیت صف از 16 MiB | UDP line بسته میشود. |
پیش از ارسال Pause، buffer، حسابداری صف و وضعیت اعلان ثبت میشوند تا callback تودرتو ترتیب FIFO را تغییر ندهد.
حجم منطقی و هزینهٔ ظرفیت buffer (شامل sbuf، padding و alignment) هرکدام سقف مستقل 16 MiB دارند؛ مقدار برابر سقف پذیرفته میشود.
با رسیدن هرکدام به 1 KiB، Pause ارسال میشود. buffer خالی نیز هزینهٔ ظرفیت دارد.
صفهای initialization و DNS هر مقصد، یک بودجهٔ مشترک برای هر line دارند.
عبور از سقف فقط همان association را میبندد و associationهای دیگر روی socket مشترک قابل استفاده میمانند.
ارسال datagram سیاست فعلی ارسال یا حذف را حفظ میکند و صف retry ندارد.
وقتی read در حالت pause است، UdpConnector datagram ورودی را در صف نمیگذارد و آن را دور میریزد.
idle timeout
UDP lineهای idle بهصورت خودکار منقضی میشوند و ادامه ترافیک deadline را تمدید
میکند. هنگام expiry، state مربوط به connector آزاد و downstream finish به node
قبلی فرستاده میشود.
نکات و هشدارها
UdpConnectorیک chain end خروجی است و بهnextنیاز ندارد.- DNS resolution async است و از
domain-strategyانتخابشده استفاده میکند. - objectهای مقصد زیر
addressesفقطaddress،portوweightدارند؛ socket optionها تنظیمات top-level هستند. fwmarkو device binding به platform وابسته هستند.fwmarkروی Windows در دسترس نیست.- وقتی read در حالت pause است، datagram ورودی بهجای قرار گرفتن در buffer دور ریخته میشود.
- source port محلی توسط connector مدیریت میشود و نباید بهعنوان شناسه line استفاده شود.
PacketsToStreamدر حال حاضر فقط packetهای IPv4 خودسازگار را bridge میکند و routing context متغیر هر packet را روی normal line خروجی کپی نمیکند. برای تبدیل IPv4 TCP/UDP به ازای flow ازPacketsToConnectionو برای semantics مستقیم packet-line routing از adapter UDP سازگار با packet استفاده کنید.- برای رفتار UDP stateless در سطح packet،
UdpStatelessSocketممکن است مناسبتر ازUdpConnectorباشد.
متادیتای 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 سرتاسری یا بهبود کاراییِ اندازهگیریشده تضمین نمیشود.
این رفتار balancing بر اساس connection یا packet روی line عادی، صفهای pause و DNS و ارسال زمان بستن را پوشش میدهد. بازنشستگی socket پیش از callbackها bindingهای peer را حذف میکند و اگر مالک مبدأ line را ببندد، drain متوقف میشود. هنگام ارسال زمان بستن، Finish دریافتشده به فرستنده منعکس نمیشود.