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

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.

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

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

فیلدنوعتوضیح
namestringنام دلخواه نود. باید داخل فایل config یکتا باشد.
typestringباید دقیقاً "UdpConnector" باشد.
settingsobjectتنظیمات مقصد و 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 domainresolve کردن 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 باید شامل باشد:

فیلدنوعتوضیح
addressstringهمان فرم‌های معتبر 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-modestring"connection"حالت انتخاب مقصد weighted: "connection" یا "packet".
large-send-bufferboolean یا عدد مثبتtrueتنظیم SO_SNDBUF روی socketهای UDP ساخته‌شده.
large-recv-bufferboolean یا عدد مثبتtrueتنظیم SO_RCVBUF روی socketهای UDP ساخته‌شده.
fwmarkintegerتنظیم نشدهاعمال socket mark به سبک Linux از طریق SO_MARK در صورت پشتیبانی.
interfacestringتنظیم نشدهمحدود کردن socketهای خروجی به یک network device محلی در صورت پشتیبانی.
source-ipstringتنظیم نشدهsocket خروجی را با یک source port موقت به source IP محلی مشخص bind می‌کند.
domain-strategystring یا integerdns.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 قدیمی هم پذیرفته می‌شوند:

مقداراستراتژی
0accept-dns-returned-order
1prefer-ipv4
2prefer-ipv6
3only-ipv4
4only-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:

  1. آدرس و پورت مقصد را برای line انتخاب می‌کند.
  2. domain-strategy را روی destination context line تنظیم می‌کند.
  3. در صورت نیاز از طریق DomainResolver داخلی domain را resolve می‌کند.
  4. بعد از اینکه مقصد یک آدرس IP شد یک socket UDP می‌سازد.
  5. اندازه bufferهای send و receive را اعمال می‌کند.
  6. تنظیمات اختیاری interface، fwmark و egress pin را اعمال می‌کند.
  7. به source-ip:0 وقتی source-ip تنظیم شده bind می‌کند، در غیر این صورت به آدرس wildcard برای خانواده آدرس انتخاب‌شده.
  8. مقصد انتخاب‌شده را به‌عنوان peer address مربوط به socket ذخیره می‌کند.
  9. خواندن از 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 باشد.