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

بخش ۳: فایل‌های config و chain‌ها

فایل core.json تنظیمات راه‌اندازی WaterWall و فهرست فایل‌های config را مشخص می‌کند. در هرکدام از این فایل‌ها، node‌ها را تعریف می‌کنید و از آن‌ها یک یا چند chain می‌سازید.

نمونه‌ای از core.json:

{
"configs": [
"configs/server.json"
]
}

تعریف خود node‌ها در فایل configs/server.json قرار می‌گیرد.

ساختار فایل config​

فایل config node‌ها در WaterWall چنین ساختاری دارد:

{
"name": "example-config",
"author": "waterwall-user",
"config-version": 1,
"core-minimum-version": 0,
"encrypted": false,
"nodes": []
}
فیلدنوعالزامیتوضیح
namestringبلهنام قابل‌تشخیص برای این فایل config.
authorstringخیردر صورت حذف، مقدار پیش‌فرض "EMPTY_AUTHOR" است.
config-versionintegerخیرشماره‌ی نسخه‌ی config که کاربر تعیین می‌کند.
core-minimum-versionintegerخیرنشانه‌ی حداقل نسخه‌ی هسته که کاربر تعیین می‌کند.
encryptedbooleanخیرconfig loader این فیلد را برای کار با config‌های رمزنگاری‌شده parse می‌کند.
nodesarrayبلهباید آرایه‌ای غیرخالی از object‌های node باشد.
variablesobjectخیرمقادیر اختیاری که می‌توان پیش از parse شدن JSON جایگزین کرد.

WaterWall پیش از parse کردن فایل config، comment‌های خطی // را نیز حذف می‌کند؛ به شرط آنکه comment داخل یک string در JSON نباشد. با این حال، بهتر است در نمونه‌های عمومی جدید comment ننویسید تا فایل برای ابزارهای دیگر نیز JSON معتبر باقی بماند.

variable‌ها​

config loader از variable‌های سطح اول پشتیبانی می‌کند:

{
"variables": {
"listen_address": "0.0.0.0",
"listen_port": 8080,
"target_host": "example.com",
"target_port": 80
},
"name": "variable-example",
"nodes": [
{
"name": "in",
"type": "TcpListener",
"settings": {
"address": $listen_address$,
"port": $listen_port$
},
"next": "out"
},
{
"name": "out",
"type": "TcpConnector",
"settings": {
"address": $target_host$,
"port": $target_port$
}
}
]
}

placeholder‌ها به‌شکل $name$ نوشته و با مقدار JSON همان variable جایگزین می‌شوند. placeholder را داخل علامت نقل‌قول نگذارید. اگر placeholder به variable‌ای اشاره کند که تعریف نشده است، فایل config load نخواهد شد.

ساختار node​

بیشتر node‌ها چنین ساختاری دارند:

{
"name": "node-name",
"type": "NodeType",
"version": 0,
"settings": {},
"next": "next-node-name"
}
فیلدنوعالزامیتوضیح
namestringبلهباید در همان فایل config یکتا باشد.
typestringبلهباید دقیقاً نام یکی از نوع‌های node باشد که load شده‌اند؛ مثلاً "TcpListener".
versionintegerخیردر صورت حذف، مقدار پیش‌فرض 0 است.
settingsobjectبسته به nodeتنظیمات مختص همان node.
nextstringبسته به جایگاهنام node بعدی در chain.

نام نوع node به بزرگی و کوچکی حروف حساس است.

قواعد chain​

با اتصال node‌ها از طریق next یک chain ساخته می‌شود:

TcpListener -> EncryptionClient -> TcpConnector

WaterWall پیش از اجرا graph را اعتبارسنجی می‌کند:

  • هر مقدار next باید به node موجودی اشاره کند.
  • نام node‌ها نباید تکراری باشد.
  • هر فایل config باید دست‌کم یک node در نقش chain head داشته باشد.
  • node‌های معمولی نباید بیرون chain باقی بمانند.
  • chain باید به node‌ای ختم شود که اجازه دارد chain end باشد.
  • chain‌های حلقوی رد می‌شوند.

در عمل، node‌های listener معمولاً chain head، node‌های connector معمولاً chain end و node‌های transform میان این دو هستند.

یک TCP forward ساده​

config زیر روی پورت TCP 8080 به‌صورت محلی گوش می‌دهد و به example.com:80 متصل می‌شود.

{
"name": "simple-tcp-forward",
"author": "waterwall-user",
"config-version": 1,
"core-minimum-version": 0,
"nodes": [
{
"name": "listen-http",
"type": "TcpListener",
"settings": {
"address": "127.0.0.1",
"port": 8080,
"nodelay": true
},
"next": "connect-example"
},
{
"name": "connect-example",
"type": "TcpConnector",
"settings": {
"address": "example.com",
"port": 80,
"domain-strategy": "prefer-ipv4"
}
}
]
}

مسیر ترافیک:

local client -> TcpListener(127.0.0.1:8080) -> TcpConnector(example.com:80)

تونل رمزنگاری‌شده‌ی TCP میان دو سرور​

برای یک تونل واقعی، معمولاً سمت client و server فایل config جداگانه دارند.

سمت client:

local app -> TcpListener -> EncryptionClient -> TcpConnector -> public server

سمت server:

public server -> TcpListener -> EncryptionServer -> TcpConnector -> private service

config client​

{
"name": "encrypted-client",
"author": "waterwall-user",
"config-version": 1,
"core-minimum-version": 0,
"nodes": [
{
"name": "local-entry",
"type": "TcpListener",
"settings": {
"address": "127.0.0.1",
"port": 1080,
"nodelay": true
},
"next": "encrypt"
},
{
"name": "encrypt",
"type": "EncryptionClient",
"settings": {
"algorithm": "chacha20-poly1305",
"password": "replace-with-a-strong-secret",
"salt": "chain-a",
"kdf-iterations": 20000
},
"next": "connect-server"
},
{
"name": "connect-server",
"type": "TcpConnector",
"settings": {
"address": "203.0.113.10",
"port": 443,
"nodelay": true
}
}
]
}

آدرس 203.0.113.10 را با IP سرور خود جایگزین کنید.

config server​

{
"name": "encrypted-server",
"author": "waterwall-user",
"config-version": 1,
"core-minimum-version": 0,
"nodes": [
{
"name": "public-entry",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 443,
"nodelay": true
},
"next": "decrypt"
},
{
"name": "decrypt",
"type": "EncryptionServer",
"settings": {
"algorithm": "chacha20-poly1305",
"password": "replace-with-a-strong-secret",
"salt": "chain-a",
"kdf-iterations": 20000
},
"next": "connect-private-service"
},
{
"name": "connect-private-service",
"type": "TcpConnector",
"settings": {
"address": "127.0.0.1",
"port": 22,
"nodelay": true
}
}
]
}

تنظیمات EncryptionClient و EncryptionServer باید با هم یکسان باشند. از یک secret قوی استفاده کنید و فایل config حاوی password واقعی را در فضای عمومی منتشر نکنید.

اشتباه‌های رایج​

استفاده از شکل نادرست حروف در type: "TcpListener" معتبر است، اما "tcplistener" نیست.

فراموش‌کردن next در میانه‌ی chain: فقط node‌ای می‌تواند next نداشته باشد که مجاز باشد در انتهای chain قرار بگیرد.

bind کردن سرویس عمومی به localhost: آدرس 127.0.0.1 فقط محلی است. اگر listener باید client‌های راه دور را بپذیرد، از 0.0.0.0 یا آدرس مشخص یک interface عمومی استفاده کنید.

ترکیب نادرست node‌های client و server: node‌های جفت باید با همتای درست خود در سمت مقابل کار کنند. برای مثال، در یک سمت EncryptionClient و در سمت دیگر EncryptionServer را با تنظیمات یکسان به کار ببرید.

قرار دادن secret‌ها در مثال‌ها: password‌ها، key‌ها و token‌های واقعی را در repository‌های عمومی قرار ندهید.