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"
}
فیلدهای الزامی
فیلدهای سطح اول:
| فیلد | نوع | توضیح |
|---|---|---|
name | String | نامی که کاربر برای Node انتخاب میکند و باید در فایل پیکربندی یکتا باشد. |
type | String | باید دقیقاً "TcpListener" باشد. |
next | String | نام Nodeای که Lineهای پذیرفتهشدهی TCP را دریافت میکند. |
فیلدهای الزامی در settings:
| فیلد | نوع | توضیح |
|---|---|---|
address | String | آدرس محلی برای Bind، مانند "0.0.0.0"، "::" یا یک IP محلی مشخص. |
port یا port-range | Number، 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 | راهاندازی متوقف میشود. |
تنظیمات اختیاری
| تنظیم | نوع | مقدار پیشفرض | توضیح |
|---|---|---|---|
nodelay | Boolean | false | گزینهی TCP_NODELAY را روی Socketهای پذیرفتهشده فعال میکند. |
large-send-buffer | Boolean یا Integer مثبت | false؛ اگر Mux در زنجیره باشد و این گزینه حذف شده باشد، true | مقدار SO_SNDBUF را روی Socketهای پذیرفتهشده تنظیم میکند. |
large-recv-buffer | Boolean یا Integer مثبت | false؛ اگر Mux در زنجیره باشد و این گزینه حذف شده باشد، true | مقدار SO_RCVBUF را روی Socketهای پذیرفتهشده تنظیم میکند. |
interface | String | تنظیم نشده | Listener را به یک Interface محلی شبکه محدود میکند. |
fwmark | Integer | تنظیم نشده | در صورت پشتیبانی، یک Socket Mark به سبک Linux اعمال میکند. |
balance-group | String | تنظیم نشده | Listenerهای همپورت را برای توزیع Clientها در یک گروه قرار میدهد. |
balance-interval | Integer، بر حسب میلیثانیه | مقدار پیشفرض Socket Manager | مدت حفظ انتخاب قبلی برای Clientهای تکراری در Balance Group. |
initial-idle-timeout-ms | Integer مثبت، بر حسب میلیثانیه | 5000 | Timeout پیش از مشاهدهی فعالیت Payload در سمت Listener. |
active-idle-timeout-ms | Integer مثبت، بر حسب میلیثانیه | 300000 | Timeout پس از مشاهدهی فعالیت در سمت Listener. |
multiport-backend | String | مقدار پیشفرض Runtime برای port-range | Backend مربوط به بازهی پیوستهی پورتها: "iptables" یا "socket". |
whitelist | Arrayای از Stringها | تنظیم نشده | IPها یا بازههای CIDR مجاز برای Client. |
blacklist | Arrayای از 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 مراحل زیر را انجام میدهد:
- Socket پذیرفتهشده را به Event Loop یک Worker متصل میکند؛
- یک Line تازه در WaterWall میسازد؛
- Line State مربوط به Listener را برای آن اتصال ذخیره میکند؛
- اطلاعات Routing را از اتصال پذیرفتهشده پر میکند؛
- رویداد
initرا در upstream به Node بعدی میفرستد؛ - خواندن از 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 تبدیل میشود. |
| از زنجیره به Client | Payload در 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 در سمت Listener | 5000 ms |
| اتصال فعال، پس از مشاهدهی فعالیت در سمت Listener | 300000 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 اتصال میپذیرد.