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

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.

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

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

فیلدنوعتوضیح
namestringنام دلخواه node. باید داخل فایل 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.

نام 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 باید شامل باشد:

فیلدنوعتوضیح
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" است.

normal line‌ها در mode برابر "packet" می‌توانند از مقصدهای IPv4 و IPv6 استفاده کنند. انتخاب socket و source port محلی به‌صورت داخلی مدیریت می‌شود.

تنظیمات اختیاری​

گزینهنوعپیش‌فرضتوضیح
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؛ در حال حاضر 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 قدیمی هم پذیرفته می‌شوند:

مقداراستراتژی
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"
}
}

این حالت پس از 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:

  1. آدرس و پورت مقصد را برای line انتخاب می‌کند.
  2. domain-strategy را روی destination context line تنظیم می‌کند.
  3. در صورت نیاز از طریق DomainResolver داخلی domain را resolve می‌کند.
  4. resource‌های UDP محلی worker را برای مقصد resolveشده آماده می‌کند.
  5. buffer‌های send/receive و تنظیمات interface، fwmark، source IP و egress pin را روی هر socket تازه‌ای که لازم باشد اعمال می‌کند.
  6. پردازش 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 KiBnode قبلی pause می‌شود.
تکمیل write‌های pendingnode قبلی resume می‌شود.
عبور حجم یا هزینهٔ ظرفیت صف از 16 MiBUDP 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 flagskNodeFlagChainEnd | kNodeFlagSupportsSplice
can_have_prevtrue
can_have_nextfalse
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayerNone
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 سرتاسری یا بهبود کاراییِ اندازه‌گیری‌شده تضمین نمی‌شود.

این رفتار balancing بر اساس connection یا packet روی line عادی، صف‌های pause و DNS و ارسال زمان بستن را پوشش می‌دهد. بازنشستگی socket پیش از callback‌ها binding‌های peer را حذف می‌کند و اگر مالک مبدأ line را ببندد، drain متوقف می‌شود. هنگام ارسال زمان بستن، Finish دریافت‌شده به فرستنده منعکس نمی‌شود.