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

TcpListener

TcpListener یک Adapter سرور TCP است. این Node یک آدرس و پورت محلی TCP را Bind می‌کند، اتصال‌های ورودی Clientها را می‌پذیرد، برای هر Socket پذیرفته‌شده یک Line در WaterWall می‌سازد و آن Line را در جهت upstream به Node بعدی زنجیره می‌فرستد.

جایگاه معمول:

client -> TcpListener -> ... -> TcpConnector -> remote service

این Node معمولاً ابتدای یک زنجیره‌ی Stream قرار می‌گیرد.

چه کاری انجام می‌دهد؟

  • روی یک پورت TCP، چند پورت مشخص یا یک بازه‌ی پیوسته از پورت‌ها گوش می‌دهد.
  • اتصال Clientهای TCP ورودی را می‌پذیرد.
  • برای هر Socket پذیرفته‌شده یک Line در WaterWall می‌سازد.
  • Payload رسیده از Client را در جهت upstream به Node بعدی می‌فرستد.
  • Payload رسیده از زنجیره در جهت downstream را روی Socket مربوط به Client می‌نویسد.
  • هنگام پذیرش اتصال، Filterهای اختیاری مانند whitelist، ‏blacklist و Balance Groupها را اعمال می‌کند.

TcpListener یک Node از نوع Chain Head است. اتصال‌ها را Clientهای خارجی TCP ایجاد می‌کنند، نه تونل قبلی در WaterWall.

نمونه‌ی پیکربندی

{
"name": "inbound-listener",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": [
443,
80,
2083
],
"nodelay": true,
"large-send-buffer": true,
"large-recv-buffer": true,
"interface": "eth0",
"fwmark": 10,
"balance-group": "public-443",
"balance-interval": 30000,
"initial-idle-timeout-ms": 5000,
"active-idle-timeout-ms": 300000,
"multiport-backend": "socket",
"whitelist": [
"192.168.1.0/24",
"2001:db8::/64"
],
"blacklist": [
"192.168.1.50/32"
]
},
"next": "next-node-name"
}

فیلدهای الزامی

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

فیلدنوعتوضیح
nameStringنامی که کاربر برای Node انتخاب می‌کند و باید در فایل پیکربندی یکتا باشد.
typeStringباید دقیقاً "TcpListener" باشد.
nextStringنام Nodeای که Lineهای پذیرفته‌شده‌ی TCP را دریافت می‌کند.

فیلدهای الزامی در settings:

فیلدنوعتوضیح
addressStringآدرس محلی برای Bind، مانند "0.0.0.0"، ‏"::" یا یک IP محلی مشخص.
port یا port-rangeNumber، ‏Array یا Range Arrayتعریف پورت Listen؛ وجود دقیقاً یکی از این دو فیلد الزامی است.

settings باید یک Object غیرخالی باشد.

تنظیم پورت

TcpListener سه حالت برای پورت دارد.

یک پورت

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

فهرست صریح پورت‌ها

{
"settings": {
"address": "0.0.0.0",
"port": [
443,
80,
2083
]
}
}

در این حالت فقط روی پورت‌های نوشته‌شده گوش داده می‌شود؛ این Array به‌معنای بازه‌ی 80 تا 2083 نیست.

بازه‌ی پیوسته‌ی پورت‌ها

{
"settings": {
"address": "0.0.0.0",
"port-range": [
40000,
40100
]
}
}

port-range باید Arrayای شامل دقیقاً دو پورت Integer مثبت به‌شکل [min, max] باشد. مقدار کمینه باید کوچک‌تر یا مساوی مقدار بیشینه باشد.

هشدار

از نمونه‌های قدیمی بازه با قالب String، مانند "40000-40100"، استفاده نکنید. Parser فعلی برای port-range یک Array دومقداری انتظار دارد.

قواعد:

قاعدهرفتار
استفاده‌ی هم‌زمان از port و port-rangeراه‌اندازی متوقف می‌شود.
مقدار Number برای portروی یک پورت گوش می‌دهد.
مقدار Array برای portروی پورت‌های صریح موجود در Array گوش می‌دهد.
مقدار Array برای port-rangeروی همه‌ی پورت‌ها از مقدار کمینه تا بیشینه گوش می‌دهد.
پورت نامعتبر، صفر، منفی یا بیشتر از 65535راه‌اندازی متوقف می‌شود.

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

تنظیمنوعمقدار پیش‌فرضتوضیح
nodelayBooleanfalseگزینه‌ی TCP_NODELAY را روی Socketهای پذیرفته‌شده فعال می‌کند.
large-send-bufferBoolean یا Integer مثبتfalse؛ اگر Mux در زنجیره باشد و این گزینه حذف شده باشد، trueمقدار SO_SNDBUF را روی Socketهای پذیرفته‌شده تنظیم می‌کند.
large-recv-bufferBoolean یا Integer مثبتfalse؛ اگر Mux در زنجیره باشد و این گزینه حذف شده باشد، trueمقدار SO_RCVBUF را روی Socketهای پذیرفته‌شده تنظیم می‌کند.
interfaceStringتنظیم نشدهListener را به یک Interface محلی شبکه محدود می‌کند.
fwmarkIntegerتنظیم نشدهدر صورت پشتیبانی، یک Socket Mark به سبک Linux اعمال می‌کند.
balance-groupStringتنظیم نشدهListenerهای هم‌پورت را برای توزیع Clientها در یک گروه قرار می‌دهد.
balance-intervalInteger، بر حسب میلی‌ثانیهمقدار پیش‌فرض Socket Managerمدت حفظ انتخاب قبلی برای Clientهای تکراری در Balance Group.
initial-idle-timeout-msInteger مثبت، بر حسب میلی‌ثانیه5000Timeout پیش از مشاهده‌ی فعالیت Payload در سمت Listener.
active-idle-timeout-msInteger مثبت، بر حسب میلی‌ثانیه300000Timeout پس از مشاهده‌ی فعالیت در سمت Listener.
multiport-backendStringمقدار پیش‌فرض Runtime برای port-rangeBackend مربوط به بازه‌ی پیوسته‌ی پورت‌ها: "iptables" یا "socket".
whitelistArrayای از Stringهاتنظیم نشدهIPها یا بازه‌های CIDR مجاز برای Client.
blacklistArrayای از Stringهاتنظیم نشدهIPها یا بازه‌های CIDR مسدود برای Client.

گزینه‌های Socket Buffer

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

مقدارمفهوم
trueاز اندازه‌ی پیش‌فرض WaterWall برای Socket Buffer بزرگ استفاده می‌کند که در حال حاضر 4194304 Byte است.
falseاین Socket Buffer را صریحاً تنظیم نمی‌کند و مقدار پیش‌فرض Kernel به کار می‌رود.
Integer مثبتهمان تعداد Byte را مستقیماً درخواست می‌کند.

اگر گزینه حذف شده باشد و زنجیره‌ی نهایی MuxClient یا MuxServer داشته باشد، TcpListener برای همان Buffer حذف‌شده به‌صورت خودکار اندازه‌ی پیش‌فرض Socket Buffer بزرگ را به کار می‌برد.

‏Interface و fwmark

interface، ‏Listener را به یک دستگاه محلی شبکه محدود می‌کند.

در Linux، ‏WaterWall از SO_BINDTODEVICE استفاده می‌کند. در Platformهایی که Bind به Device را پشتیبانی نمی‌کنند، WaterWall در صورت امکان روی آدرس IPv4 همان Interface Bind می‌شود.

fwmark در Platformهای پشتیبانی‌شده از SO_MARK استفاده می‌کند. این قابلیت به Platform وابسته است و در Windows در دسترس نیست.

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

Listener محلی برای آزمایش

{
"name": "local-entry",
"type": "TcpListener",
"settings": {
"address": "127.0.0.1",
"port": 8080
},
"next": "outbound"
}

این Listener فقط Clientهای همان دستگاه را می‌پذیرد.

Listener عمومی روی VPS

{
"name": "public-entry",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 443,
"nodelay": true,
"initial-idle-timeout-ms": 5000,
"active-idle-timeout-ms": 300000
},
"next": "tls-server"
}

این Listener، ‏Clientهای IPv4 را روی همه‌ی Interfaceهای محلی می‌پذیرد. پورت انتخاب‌شده باید هم در Firewall خود VPS و هم در Firewall شرکت ارائه‌دهنده مجاز باشد.

Listener چندپورتی

{
"name": "multiport-entry",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": [
443,
8443,
2083
],
"nodelay": true
},
"next": "router"
}

برای Array صریح port، به‌ازای هر پورت یک Socket جداگانه در حالت Listen ساخته می‌شود.

Listener روی بازه‌ی پورت

{
"name": "range-entry",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port-range": [
40000,
40100
],
"multiport-backend": "socket"
},
"next": "router"
}

برای port-range، ‏Runtime می‌تواند با Backend نوع socket به‌ازای هر پورت یک Socket در حالت Listen بسازد، یا با Backend نوع iptables از یک Socket اصلی و Ruleهای Redirect استفاده کند. هرگاه به Backend مشخصی نیاز دارید، multiport-backend را صریحاً تنظیم کنید.

مسیر پذیرش اتصال

وقتی یک Client مبتنی بر TCP متصل می‌شود، TcpListener مراحل زیر را انجام می‌دهد:

  1. Socket پذیرفته‌شده را به Event Loop یک Worker متصل می‌کند؛
  2. یک Line تازه در WaterWall می‌سازد؛
  3. Line State مربوط به Listener را برای آن اتصال ذخیره می‌کند؛
  4. اطلاعات Routing را از اتصال پذیرفته‌شده پر می‌کند؛
  5. رویداد init را در upstream به Node بعدی می‌فرستد؛
  6. خواندن از Socket مربوط به Client را آغاز می‌کند.

Source Context مربوط به Line از اتصال پذیرفته‌شده پر می‌شود. IP سمت مقابل در src_ctx ثبت می‌شود؛ پورت محلی پذیرنده‌ی اتصال در src_ctx.port و local_listener_port قرار می‌گیرد؛ و پورت واقعی مبدأ TCP مربوط به Client به‌صورت جداگانه در peer_source_port نگه داشته می‌شود.

جریان داده

client socket -> TcpListener -> next node
client socket <- TcpListener <- next node

رفتار هر جهت:

جهترفتار
از Client به زنجیرهداده‌ی خوانده‌شده از Socket به Payload در upstream برای next تبدیل می‌شود.
از زنجیره به ClientPayload در downstream روی Socket پذیرفته‌شده نوشته می‌شود.

Node بعدی باید انتظار ترافیک Clientهای پذیرفته‌شده را داشته باشد و پاسخ‌ها را در جهت downstream برگرداند.

Backpressure و صف نوشتن

TcpListener از پردازش در برابر Socket مربوط به Clientی که کند است یا موقتاً امکان نوشتن ندارد محافظت می‌کند.

رفتار:

  • اگر عملیات Write بلافاصله کامل نشود، بافرهای خروجی در صف قرار می‌گیرند.
  • وقتی داده‌ی موجود در صف از 1 KB عبور کند، Node بعدی Pause می‌شود.
  • وقتی Socket دوباره قابل‌نوشتن باشد، بافرهای صف Flush و Node بعدی Resume می‌شود.
  • اگر داده‌ی موجود در صف از 16 MB عبور کند، اتصال بسته می‌شود.

این سازوکار اجازه نمی‌دهد یک Client کند باعث رشد نامحدود حافظه شود.

رفتار Idle Timeout

هر اتصال پذیرفته‌شده در یک جدول Idle محلی Worker ردیابی می‌شود.

مرحلهTimeout پیش‌فرض
اتصال تازه‌پذیرفته‌شده، پیش از فعالیت Payload در سمت Listener5000 ms
اتصال فعال، پس از مشاهده‌ی فعالیت در سمت Listener300000 ms

اگر Timeout اتصال تمام شود، Socket و Line بسته می‌شوند.

مقادیر مرحله‌ی اولیه و فعال با گزینه‌های زیر تنظیم می‌شوند:

{
"settings": {
"initial-idle-timeout-ms": 5000,
"active-idle-timeout-ms": 300000
}
}

در زنجیره‌هایی مانند TcpListener -> TlsServer، فعالیت سمت Listener با کامل‌شدن TLS Handshake یکسان نیست. Clientی که فقط بخشی از یک TLS Record را می‌فرستد ممکن است وارد Timeout مرحله‌ی فعال شود، در حالی که TlsServer هنوز منتظر Byteهای دیگر Handshake است. هنگام تنظیم Fallback مقاوم در برابر Probe در TLS، این Timeoutها را با زمان‌بندی سرویس عمومی‌ای که می‌خواهید شبیه آن باشید هماهنگ نگه دارید.

Balance Groupها

balance-group این Listener را در کنار Listenerهای سازگار دیگر روی همان پورت در یک گروه قرار می‌دهد.

وقتی چند Listener، ‏balance-group و پورت یکسانی دارند، Socket Manager با Hash کردن IP مربوط به Client یکی از آن‌ها را برای Client تازه انتخاب و این انتخاب را به‌خاطر می‌سپارد. در طول balance-interval، اتصال‌های بعدی همان IP همچنان به همان Listener فرستاده می‌شوند.

نمونه:

{
"settings": {
"address": "0.0.0.0",
"port": 443,
"balance-group": "public-443",
"balance-interval": 30000
}
}

Whitelist و Blacklist

whitelist و blacklist، ‏Arrayهایی از IP یا CIDR در قالب String می‌پذیرند و هر دو IPv4 و IPv6 پشتیبانی می‌شوند.

نمونه:

{
"settings": {
"whitelist": [
"10.0.0.0/8",
"192.168.1.20/32",
"2001:db8::/64"
],
"blacklist": [
"10.0.0.13/32"
]
}
}

رفتار:

پیکربندینتیجه
هیچ فهرستی وجود ندارداین Listener روی IP مربوط به Client Filter اعمال نمی‌کند.
whitelist وجود داردفقط Clientهایی که IP آن‌ها با فهرست منطبق است پذیرفته می‌شوند.
blacklist وجود داردClientهایی که IP آن‌ها با فهرست منطبق است رد می‌شوند.
هر دو وجود دارندClient باید، در صورت وجود Whitelist، با آن منطبق باشد و نباید با Blacklist تطابق داشته باشد.

اگر چند Listener روی یک پورت ثبت شده باشند، ممکن است Listener دیگری همان اتصال را بگیرد؛ به شرط آنکه Filter آن منطبق باشد و Filter این Listener منطبق نباشد.

نکات مربوط به Multiport Backend

در Array صریح port، ‏WaterWall هر عضو را یک پورت مستقل در نظر می‌گیرد و به‌ازای هر پورت یک Socket در حالت Listen می‌سازد.

برای port-range، ‏WaterWall می‌تواند از Backendهای زیر استفاده کند:

Backendتوضیح
"socket"به‌ازای هر پورت یک Socket در حالت Listen می‌سازد؛ رفتارش ساده و قابل‌درک است.
"iptables"از Ruleهای Redirect استفاده می‌کند و می‌تواند برای بازه‌های بزرگ مناسب باشد، اما به محیط Host و قواعد Firewall وابسته است.

برای بازه‌های کوچک معمولاً Debugکردن socket آسان‌تر است. در بازه‌های بزرگ، اگر استقرار شما کنترل Ruleهای سیستم را در اختیار دارد، iptables می‌تواند تعداد Socketها را کاهش دهد.

اولویت Filterها و مسیرهای پیش‌فرض

وقتی چند Node از نوع TcpListener یک پورت محلی را پوشش می‌دهند، Socket Manager همه را هم‌ارز در نظر نمی‌گیرد و ابتدا Filterهای دقیق‌تر را بررسی می‌کند. Listener دارای whitelist یا blacklist از Listener مشابهی که Filter مربوط به IP ندارد اولویت بیشتری دارد.

این رفتار امکان ساخت الگوی مسیر پیش‌فرض را فراهم می‌کند:

same public port
├─ matching filtered listener -> special path
└─ unfiltered listener -> default path

نمونه:

{
"nodes": [
{
"name": "trusted-entry",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 443,
"whitelist": [
"203.0.113.10/32",
"2001:db8:100::/48"
]
},
"next": "trusted-path"
},
{
"name": "default-entry",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 443
},
"next": "default-path"
}
]
}

در این ساختار، trusted-entry مسئول Clientهایی است که با trusted-entry.whitelist تطابق دارند. Clientهای دیگر به default-entry می‌رسند، زیرا Listener بدون Filter نقش مسیر پیش‌فرض آن پورت را دارد.

می‌توانید همین الگو را با blacklist به کار ببرید تا یک بازه را از مسیری خارج کنید و، در صورت تطابق Filterهای Listener دیگر، اتصال را به آن بسپارید.

Filterهای TcpListener فقط IP/CIDR را می‌پذیرند. نام‌های GeoIP، دسته‌بندی دامنه، SNI، ‏HTTP Host و Protocol Sniffing پشتیبانی نمی‌شوند. برای مسیریابی بر اساس کشور، مانند geoip:ir، ابتدا اتصال را بپذیرید و سپس از Node نوع Router استفاده کنید.

اگر balance-group فعال باشد، Listenerهای منطبق از طریق منطق Balance Group انتخاب می‌شوند و مانند یک شاخه‌ی First-match ساده رفتار نمی‌کنند. برای مسیریابی قطعی بر اساس IP، الگوی اولویت/مسیر پیش‌فرض را با Listenerهای Filterشده‌ی معمولی به کار ببرید.

نکات و محدودیت‌ها

  • TcpListener برای نقطه‌ی ورود ترافیک طراحی شده است.
  • Parser برای port یک Number یا Array صریحی از شماره‌ی پورت‌ها انتظار دارد.
  • Parser برای port-range یک Range Array دومقداری انتظار دارد.
  • multiport-backend فقط برای Listenerهای دارای port-range پیوسته اثر دارد.
  • fwmark و Bindشدن به Device به Platform وابسته‌اند.
  • Listener متصل به 127.0.0.1 فقط Clientهای محلی را می‌پذیرد.
  • Listener متصل به 0.0.0.0، با رعایت Firewall سیستم‌عامل و شرکت ارائه‌دهنده، روی همه‌ی Interfaceهای IPv4 اتصال می‌پذیرد.