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

TcpConnector

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

جایگاه رایج:

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

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

قابلیت‌ها

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

TcpConnector در انتهای chain قرار می‌گیرد، با upstream Init راه می‌افتد و به نود 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نام دلخواه نود. باید داخل فایل 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.

مقادیر معتبر برای 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؛ در حال حاضر 4194304 بایت.
falseتنظیم نکردن صریح اندازه buffer و استفاده از مقدار پیش‌فرض kernel.
عدد مثبتدرخواست مستقیم آن تعداد بایت.

اگر یکی از این گزینه‌ها را ننویسید و chain نهایی شامل MuxClient یا MuxServer باشد، TcpConnector به‌طور خودکار buffer بزرگ پیش‌فرض را برای همان جهت فعال می‌کند. مقدار صریح false همچنان مانع تنظیم اندازه buffer می‌شود.

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"
}
}

این حالت پس از نودهایی کاربرد دارد که 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 به نود قبلی فرستاده می‌شود.
  5. payloadهای upstream روی socket نوشته می‌شوند و داده‌های خوانده‌شده از socket به payload downstream تبدیل می‌شوند.

اگر آماده‌سازی مقصد، DNS resolution، ساخت socket یا اتصال ناموفق باشد، line در جهت downstream و به سمت نود قبلی 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/32 مثل یک آدرس ثابت رفتار می‌کند. prefixهای بالاتر از /32 رد می‌شوند.
IPv664 تا 128/64 شصت‌وچهار بیت پایینی را تصادفی می‌کند؛ prefixهای کوتاه‌تر از /64 پذیرفته نمی‌شوند.

اگر بازه محاسبه‌شده صفر باشد، مقصد مثل یک آدرس ثابت رفتار می‌کند.

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

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

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

وضعیت صفرفتار
بیشتر از 1 KB در صفنود قبلی pause می‌شود.
تکمیل write‌های pendingنود قبلی resume می‌شود.
بیشتر از 16 MB در صفاتصال بسته و line finish می‌شود.

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

idle timeout

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

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

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

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