UdpConnector
UdpConnector adapter خروجی UDP است. یک UDP socket محلی میسازد، address و port مقصد را انتخاب میکند و datagramها را میان نود قبلی و peer راه دور جابهجا میسازد.
جایگاه رایج:
UdpListener -> ... -> UdpConnector -> سرویس UDP راه دور
این نود معمولاً انتهای یک UDP stream-style chain است.
قابلیتها
- ساخت یک UDP socket که به port موقت محلی bind شده است.
- انتخاب address و port مقصد از تنظیمات ثابت، مقصدهای وزندار یا routing context.
- resolve کردن نام domain از طریق یک
DomainResolverداخلی و async. - ارسال payloadهای upstream از نود قبلی به remote UDP peer.
- ارسال datagramهای UDP دریافتشده بهصورت downstream به نود قبلی.
- دور ریختن datagramهای peer ناشناس در balance mode برابر
"connection". - ردیابی UDP lineها با idle timeout.
- اعمال socket optionهای اختیاری مثل socket bufferها،
SO_MARK، device binding و source-IP binding در صورت پشتیبانی.
UdpConnector در انتهای chain قرار میگیرد، با upstream Init راه میافتد و به نود 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 | نام دلخواه نود. باید داخل فایل 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. |
مقادیر معتبر برای 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) به پورتهای معتبر نیاز دارد و x نباید از y بزرگتر باشد.
مقصدهای 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" است.
در mode برابر "packet" همچنان برای هر WaterWall line فقط یک UDP socket ساخته میشود، پس تمام مقصدها باید با خانواده آدرس همان socket سازگار باشند. برای نمونه، مقصدهای فقط IPv4 و فقط IPv6 را در یک فهرست packet-balanced مخلوط نکنید، مگر اینکه socket انتخابشده بتواند به همه آنها بفرستد.
تنظیمات اختیاری
| گزینه | نوع | پیشفرض | توضیح |
|---|---|---|---|
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؛ در حال حاضر 4194304 بایت. |
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"
}
}
این حالت پس از نودهایی کاربرد دارد که routing context را بهصورت پویا پر یا بازنویسی میکنند.
پورت مقصد تصادفی
{
"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 میکند. - بعد از اینکه مقصد یک آدرس IP شد یک socket UDP میسازد.
- اندازه bufferهای send و receive را اعمال میکند.
- تنظیمات اختیاری
interface،fwmarkو egress pin را اعمال میکند. - به
source-ip:0وقتیsource-ipتنظیم شده bind میکند، در غیر این صورت به آدرس wildcard برای خانواده آدرس انتخابشده. - مقصد انتخابشده را بهعنوان peer address مربوط به socket ذخیره میکند.
- خواندن از socket را آغاز میکند و downstream
Estرا به نود قبلی میفرستد.
UDP در اینجا handshake اتصال ندارد. Est فقط آماده بودن UDP socket محلی را نشان میدهد، نه پاسخ دادن 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، packetهای بعدی همان line از آدرس بهدستآمده استفاده میکنند
- cache زماندار DNS یا DNS request جدا برای هر packet وجود ندارد
جریان داده
از نود قبلی به remote peer:
payload upstream -> UdpConnector -> ارسال UDP
از remote peer به نود قبلی:
دریافت UDP -> UdpConnector -> payload downstream
در mode برابر "connection" فقط datagramهای peer راه دور انتخابشده پذیرفته میشوند و داده peerهای دیگر نادیده گرفته خواهد شد.
در mode برابر "packet"، socket پاسخ هر مقصد packet-balanced را میپذیرد، حتی اگر datagramها خارج از ترتیب برگردند.
کنترل جریان و buffering
در حالی که DNS یا resolution مقصد packet در انتظار است، UdpConnector ممکن است payloadهای upstream را صف کند.
آستانههای صف فعلی:
| وضعیت صف | رفتار |
|---|---|
بیشتر از 1 KB در صف | نود قبلی pause میشود. |
| تکمیل writeهای pending | نود قبلی resume میشود. |
بیشتر از 16 MB در صف | UDP line بسته میشود. |
وقتی read در حالت pause است، UdpConnector datagram ورودی را در صف نمیگذارد و آن را دور میریزد.
idle timeout
هر UDP line در یک جدول idle ردیابی میشود.
timeoutهای فعلی:
| وضعیت | timeout |
|---|---|
| بعد از مقداردهی اولیه | حدود 30 ثانیه |
| بعد از ادامه ترافیک | حدود 300 ثانیه |
اگر UDP line منقضی شود، socket بسته میشود و downstream finish به نود قبلی فرستاده میشود.
نکات و هشدارها
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 دور ریخته میشود.
- برای رفتار UDP stateless در سطح packet،
UdpStatelessSocketممکن است مناسبتر ازUdpConnectorباشد.