بخش ۳: فایلهای 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": []
}
| فیلد | نوع | الزامی | توضیح |
|---|---|---|---|
name | string | بله | نام قابلتشخیص برای این فایل config. |
author | string | خیر | در صورت حذف، مقدار پیشفرض "EMPTY_AUTHOR" است. |
config-version | integer | خیر | شمارهی نسخهی config که کاربر تعیین میکند. |
core-minimum-version | integer | خیر | نشانهی حداقل نسخهی هسته که کاربر تعیین میکند. |
encrypted | boolean | خیر | config loader این فیلد را برای کار با configهای رمزنگاریشده parse میکند. |
nodes | array | بله | باید آرایهای غیرخالی از objectهای node باشد. |
variables | object | خیر | مقادیر اختیاری که میتوان پیش از 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"
}
| فیلد | نوع | الزامی | توضیح |
|---|---|---|---|
name | string | بله | باید در همان فایل config یکتا باشد. |
type | string | بله | باید دقیقاً نام یکی از نوعهای node باشد که load شدهاند؛ مثلاً "TcpListener". |
version | integer | خیر | در صورت حذف، مقدار پیشفرض 0 است. |
settings | object | بسته به node | تنظیمات مختص همان node. |
next | string | بسته به جایگاه | نام 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های عمومی قرار ندهید.