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

TcpConnector

TcpConnector adapter خروجی TCP است. line را از node قبلی می‌گیرد، مقصد را از تنظیمات node یا routing context انتخاب می‌کند، اتصال TCP را باز می‌کند و payload‌ها را میان chain و server راه دور جابه‌جا می‌سازد.

جایگاه رایج:

TcpListener -> ... -> TcpConnector -> سرویس TCP راه دور

این node معمولاً انتهای یک stream chain است.

قابلیت‌ها​

  • انتخاب آدرس و پورت مقصد برای هر line.
  • resolve کردن نام domain از طریق یک DomainResolver داخلی و async.
  • باز کردن یک socket TCP خروجی.
  • نوشتن payload‌های upstream از node قبلی روی socket راه دور.
  • فرستادن داده‌های خوانده‌شده از socket راه دور به‌عنوان payload downstream به node قبلی.
  • فرستادن downstream Est پس از برقراری موفق اتصال TCP خروجی.
  • اعمال گزینه‌های socket مانند TCP_NODELAY، TCP_FASTOPEN، اندازه buffer‌ها، SO_MARK، device binding و source-IP binding در صورت پشتیبانی platform.

TcpConnector در انتهای chain قرار می‌گیرد، با upstream Init راه می‌افتد و به node next نیاز ندارد.

نمونه تنظیم​

{
"name": "outbound-tcp",
"type": "TcpConnector",
"settings": {
"address": "example.com",
"port": 443,
"nodelay": true,
"fastopen": false,
"large-send-buffer": true,
"large-recv-buffer": 4194304,
"fwmark": 10,
"interface": "eth0",
"source-ip": "192.0.2.10",
"domain-strategy": "prefer-ipv4"
}
}

نمونه چند مقصد وزن‌دار​

{
"name": "outbound-tcp",
"type": "TcpConnector",
"settings": {
"addresses": [
{
"address": "192.0.2.10",
"port": 443,
"weight": 5,
"nodelay": true,
"interface": "eth0",
"domain-strategy": "prefer-ipv4"
},
{
"address": "example.org",
"port": "dest_context->port",
"weight": 1,
"source-ip": "198.51.100.10",
"domain-strategy": "only-ipv4"
},
{
"address": "2001:db8:1::/64",
"port": 443,
"weight": 1,
"interface": null,
"source-ip": null,
"domain-strategy": "only-ipv6"
}
],
"nodelay": true,
"fastopen": false,
"large-send-buffer": true,
"large-recv-buffer": true,
"interface": "eth0",
"domain-strategy": "prefer-ipv4"
}
}

برای هر line جدید، یکی از object‌های addresses با احتمالی متناسب با weight آن انتخاب می‌شود. گزینه‌هایی که در object مقصد نوشته نشده‌اند از سطح بالای settings به ارث می‌رسند. برای interface و source-ip می‌توانید با مقدار null، مقدار به‌ارث‌رسیده را فقط برای همان مقصد غیرفعال کنید.

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

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

فیلدنوعتوضیح
namestringنام دلخواه node. باید داخل فایل config یکتا باشد.
typestringباید دقیقاً "TcpConnector" باشد.
settingsobjectتنظیمات مقصد و socket.

settings باید دقیقاً از یک سبک مقصد استفاده کند:

سبکفیلدهای لازمتوضیح
مقصد تکیaddress و portهر line از همان قانون مقصد استفاده می‌کند.
مقصدهای وزن‌دارaddressesبرای هر line یک مقصد بر اساس وزن انتخاب می‌شود.

addresses را با address یا port top-level ترکیب نکنید.

انتخاب مقصد​

مقصد تکی​

از address و port مستقیماً زیر settings استفاده کنید.

{
"settings": {
"address": "93.184.216.34",
"port": 443
}
}

مقادیر معتبر برای address:

مقداررفتار
string IPv4اتصال به آن آدرس IPv4.
string IPv6اتصال به آن آدرس IPv6.
string domainresolve کردن domain پیش از اتصال.
"src_context->address"استفاده از آدرس source در routing context مربوط به line.
"dest_context->address"استفاده از آدرس destination در routing context مربوط به line.

نام domain ثابت باید بین ۱ تا ۲۵۵ بایت باشد.

مقادیر معتبر برای port:

مقداررفتار
عدداتصال به آن پورت ثابت. باید 1 تا 65535 باشد.
"src_context->port"استفاده از پورت source در routing context مربوط به line.
"dest_context->port"استفاده از پورت destination در routing context مربوط به line.

random(x,y) در parser فعلی برای TcpConnector پشتیبانی نمی‌شود.

مقصدهای weighted​

برای انتخاب یک مقصد وزن‌دار به‌ازای هر line از addresses استفاده کنید.

{
"settings": {
"addresses": [
{
"address": "192.0.2.10",
"port": 443,
"weight": 3
},
{
"address": "198.51.100.20",
"port": 443,
"weight": 1
}
]
}
}

هر object باید شامل باشد:

فیلدنوعتوضیح
addressstringهمان فرم‌های معتبر address در مقصد تکی.
portعدد یا string خاصهمان فرم‌های معتبر port در مقصد تکی.
weightعدد صحیح مثبتوزن نسبی این مقصد هنگام انتخاب.

هر object می‌تواند گزینه‌های زیر را هم برای همان مقصد تغییر دهد:

گزینه
nodelay
fastopen
large-send-buffer
large-recv-buffer
fwmark
interface
source-ip
domain-strategy

parser برای سازگاری کلید قدیمی و غلط‌نویسی‌شده adresses را هم می‌پذیرد، اما در config جدید از addresses استفاده کنید و هر دو نام را هم‌زمان ننویسید.

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

گزینهنوعپیش‌فرضتوضیح
nodelaybooleantrueفعال‌سازی TCP_NODELAY روی socket‌های خروجی.
fastopenbooleanfalseدر صورت پشتیبانی platform، TCP_FASTOPEN را درخواست می‌کند.
large-send-bufferboolean یا عدد مثبتfalse، یا true اگر mux وجود داشته باشد و حذف شده باشدتنظیم SO_SNDBUF.
large-recv-bufferboolean یا عدد مثبتfalse، یا true اگر mux وجود داشته باشد و حذف شده باشدتنظیم SO_RCVBUF.
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.

هنگام استفاده از addresses می‌توانید این فیلدها را در سطح بالای settings به‌عنوان پیش‌فرض بنویسید؛ هر object مقصد می‌تواند مقدار خودش را جایگزین کند.

گزینه‌های socket buffer​

large-send-buffer و large-recv-buffer مقادیر زیر را می‌پذیرند:

مقدارمعنا
trueاستفاده از buffer بزرگ پیش‌فرض WaterWall برای socket؛ در حال حاضر 16777216 بایت.
falseتنظیم نکردن صریح اندازه buffer و استفاده از مقدار پیش‌فرض kernel.
عدد مثبتدرخواست مستقیم آن تعداد بایت.

اگر chain نهایی شامل MuxClient یا MuxServer باشد، گزینه‌های حذف‌شده‌ی buffer اندازه‌ی پیش‌فرض 16 MiB را صریحاً درخواست می‌کنند، مستقل از core misc.tcp-tune و موفقیت اجرای sysctl. در نبود Mux در chain، گزینه‌های حذف‌شده به kernel واگذار می‌شوند. مقادیر صریح، از جمله false، در هر دو حالت اولویت دارند. مقصدهای TcpConnector برای گزینه‌های حذف‌شده مقدار مؤثر سطح اصلی را به ارث می‌برند.

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

در addresses وزن‌دار، هر object می‌تواند domain-strategy مخصوص line‌هایی را تعریف کند که همان مقصد را انتخاب کرده‌اند.

interface، source IP و egress pinning​

interface socket خروجی را به یک device محلی محدود می‌کند.

در Linux، WaterWall در صورت امکان از SO_BINDTODEVICE استفاده می‌کند. روی platform‌های بدون device binding، socket به آدرس IPv4 مربوط به interface bind می‌شود. اگر آدرسی پیدا نشود، راه‌اندازی اتصال شکست می‌خورد.

source-ip socket خروجی را قبل از connect به یک source IP محلی مشخص bind می‌کند. خانواده آدرس باید با خانواده مقصد مطابقت داشته باشد.

اگر loop protection در TunDevice یک egress pin خودکار ایجاد کرده باشد و interface را ننوشته باشید، TcpConnector از همان pin استفاده می‌کند. source-ip به‌تنهایی آن interface را تغییر نمی‌دهد؛ یا source IP را از همان interface انتخاب کنید، یا interface را صریحاً بنویسید.

نمونه‌های رایج​

مقصد راه دور ثابت​

{
"name": "out",
"type": "TcpConnector",
"settings": {
"address": "example.com",
"port": 443,
"domain-strategy": "prefer-ipv4"
}
}

اتصال به destination context موجود​

{
"name": "context-out",
"type": "TcpConnector",
"settings": {
"address": "dest_context->address",
"port": "dest_context->port"
}
}

این حالت پس از node‌هایی کاربرد دارد که metadata مقصد را می‌خوانند، بازنویسی یا مسیریابی می‌کنند.

bind به یک آدرس محلی مشخص​

{
"name": "egress-ip-a",
"type": "TcpConnector",
"settings": {
"address": "203.0.113.10",
"port": 443,
"source-ip": "192.0.2.10"
}
}

استخر egress weighted​

{
"name": "egress-pool",
"type": "TcpConnector",
"settings": {
"addresses": [
{
"address": "edge-a.example.net",
"port": 443,
"weight": 4,
"interface": "eth0"
},
{
"address": "edge-b.example.net",
"port": 443,
"weight": 1,
"interface": "eth1"
}
],
"domain-strategy": "prefer-ipv4"
}
}

رفتار chain​

هنگام upstream Init، TcpConnector state مربوط به line را مقداردهی می‌کند، قانون مقصد را برمی‌گزیند، address و port را در destination context می‌گذارد و domain strategy را تنظیم می‌کند.

جریان داخلی:

  1. تنظیمات مقصد شامل address، port، گزینه‌های socket و اطلاعات اختیاری CIDR randomization انتخاب می‌شوند.
  2. DomainResolver داخلی مقصد را در صورت domain بودن resolve می‌کند.
  3. پس از تبدیل مقصد به IP و port، TcpConnector socket خروجی را می‌سازد و اتصال async را آغاز می‌کند.
  4. با موفقیت اتصال، downstream Est به node قبلی فرستاده می‌شود.
  5. payload‌های upstream روی socket نوشته می‌شوند و داده‌های خوانده‌شده از socket به payload downstream تبدیل می‌شوند.

اگر آماده‌سازی مقصد، DNS resolution، ساخت socket یا اتصال ناموفق باشد، line در جهت downstream و به سمت node قبلی finish می‌شود.

domain resolution​

resolve کردن domain به‌شکل async انجام می‌شود. TcpConnector یک DomainResolver داخلی می‌سازد و پیش از resolution، domain-strategy انتخاب‌شده را در destination context ذخیره می‌کند.

payload‌های رسیده در زمان انتظار برای DNS نزد resolver می‌مانند. payload‌هایی که پس از موفقیت DNS و پیش از برقراری TCP می‌رسند نیز تا آماده شدن socket در TcpConnector صف می‌شوند.

CIDR randomization روی IP‌های ثابت​

یک address ثابت IP می‌تواند پسوند CIDR داشته باشد. در این حالت TcpConnector پیش از اتصال، host part را به‌صورت تصادفی انتخاب می‌کند.

{
"settings": {
"address": "198.51.100.0/24",
"port": 443
}
}
{
"settings": {
"address": "2001:db8:1::/64",
"port": 443
}
}

پسوندهای CIDR فقط روی آدرس‌های IP ثابت معتبر هستند. روی domain‌ها، "src_context->address" یا "dest_context->address" معتبر نیستند.

قوانین پیاده‌سازی:

خانواده آدرسطول prefix معتبرتوضیح
IPv40 تا 32/0 هر ۳۲ بیت آدرس را تصادفی می‌کند؛ /32 آدرس را ثابت نگه می‌دارد.
IPv664 تا 128/64 هر ۶۴ بیت بخش host را با حفظ prefix شبکه تصادفی می‌کند؛ /128 آدرس را ثابت نگه می‌دارد.

پسوند باید فقط شامل رقم‌های ده‌دهی باشد. prefix خالی، نامعتبر یا خارج از بازه رد می‌شود. prefix طولانی‌تر فقط بیت‌های باقی‌ماندهٔ بخش host را تصادفی می‌کند.

کنترل جریان و buffering​

در حالی که DNS، connect یا socket write‌ها در انتظار هستند، TcpConnector ممکن است payload‌های upstream را صف کند.

آستانه‌های فعلی:

وضعیت صفرفتار
رسیدن حجم یا هزینهٔ ظرفیت صف به 1 KiBnode قبلی pause می‌شود.
تکمیل write‌های pendingnode قبلی resume می‌شود.
عبور حجم یا هزینهٔ ظرفیت صف از 16 MiBاتصال بسته و line finish می‌شود.

پیش از ارسال Pause، buffer، حسابداری صف و وضعیت اعلان ثبت می‌شوند تا callback تو‌در‌تو ترتیب FIFO را تغییر ندهد. حجم منطقی و هزینهٔ ظرفیت buffer (شامل sbuf، padding و alignment) هرکدام سقف مستقل 16 MiB دارند؛ مقدار برابر سقف پذیرفته می‌شود. با رسیدن هرکدام به 1 KiB، Pause ارسال می‌شود. buffer خالی نیز هزینهٔ ظرفیت دارد. بودجهٔ TCP صف adapter و buffer فعال WIO را با هم حساب می‌کند. نوشتن جزئی تا زمان آزاد شدن buffer، هزینهٔ ظرفیت آن را کم نمی‌کند. عبور از سقف، همان اتصال را از طریق مالک line می‌بندد.

این آستانه‌ها نمی‌گذارند هنگام کند بودن server راه دور یا آماده نبودن اتصال، مصرف حافظه بی‌حد رشد کند.

مهلت برقراری اتصال​

هر تلاش برای برقراری اتصال خروجی TCP یک مهلت 60 ثانیه‌ای دارد که با پیش‌فرض proxy_connect_timeout در NGINX هماهنگ است. این مهلت پس از Resolve شدن آدرس و با آغاز تلاش اتصال TCP شروع می‌شود و پس از برقراری اتصال لغو می‌شود. خطاهای فوری اتصال همچنان می‌توانند line را زودتر ببندند. این مهلت از idle timeout اولیه‌ی listener و محدودیت پاک‌سازی اتصال‌های بی‌فعالیت connector جداست؛ TcpConnector مرحله‌ی idle جداگانه‌ای برای انتظار اولین payload ندارد.

idle timeout​

هر اتصال خروجی در جدول idle همان worker دنبال می‌شود. timeout فعلی نزدیک به 600 ثانیه است و هر read یا write آن را تمدید می‌کند.

این محدودیت ده‌دقیقه‌ای برای پاک‌سازی اتصال‌های بی‌فعالیت با timeout فعال پیش‌فرض TcpListener هماهنگ است. اگر مقصد زودتر اتصال را ببندد، finish بدون انتظار تا پایان مهلت idle به سمت downstream فرستاده می‌شود. این مقدار سقف NGINX نیست؛ اگر مقصد fallback بی‌فعالیتی طولانی‌تری را مجاز بداند، محدودیت idle هر دو adapter باید آن را پوشش دهد.

اگر اتصال منقضی شود، socket بسته می‌شود و downstream finish به node قبلی فرستاده می‌شود.

نکات و هشدارها​

  • TcpConnector یک chain end خروجی است و به next نیاز ندارد.
  • DNS resolution async است و از domain-strategy انتخاب‌شده استفاده می‌کند.
  • fwmark، TCP_FASTOPEN و device binding به platform وابسته هستند.
  • fwmark روی Windows در دسترس نیست.
  • source-ip باید یک آدرس IP معتبر باشد و باید با خانواده آدرس مقصد مطابقت داشته باشد.
  • کلید قدیمی adresses برای سازگاری پذیرفته می‌شود، اما addresses نام‌گذاری مستند‌شده است.

متادیتای node​

متادیتای برگرفته از کد منبع:

ویژگیمقدار
node flagskNodeFlagChainEnd | kNodeFlagSupportsSplice
can_have_prevtrue
can_have_nextfalse
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayerNone
required_padding_left0 بایت