UserController
UserController نودی داخلی و پیشرفته است که محدودیتهای کاربران احراز هویتشده را در chainهای سرویسدهنده اعمال میکند.
معمولاً لازم نیست این نود را دستی در chain قرار دهید. نودهای server-side مانند Socks5Server، TrojanServer و VlessServer در حالت authentication مبتنی بر database، خودشان یک UserController داخلی میسازند. فقط زمانی سراغ تنظیم دستی بروید که tunnel سفارشی شما lineAddUser() را فراخوانی میکند و lifecycle مربوط به user handle را میشناسد.
کاری که انجام میدهد
UserController پس از نودی قرار میگیرد که user را احراز هویت کرده و یک user_handle_t معتبر روی line گذاشته است. برای هر line مدیریتشده، این نود:
- فعال بودن user را بررسی میکند
- تاریخ انقضای user را میسنجد
- یک connection slot را رزرو میکند
- در صورت وجود source IP، یک IP slot را هم رزرو میکند
- byteهای upstream و downstream را در آمار ترافیک حساب میکند
- اگر user غیرفعال، منقضی، حذف یا over-quota شود lineهای فعال او را میبندد
- پس از finish شدن line، connection slot و IP slot را آزاد میکند
این نود login protocol را تجزیه نمیکند و database اصلی کاربران را هم نگه نمیدارد. lookup و accounting از طریق یک AuthenticationClient موجود انجام میشوند.
جایگاه رایج
برای server nodeهای عادی دارای authentication، آن را دستی اضافه نکنید:
TcpListener -> Socks5Server -> TcpConnector
در حالت authenticated، Socks5Server خودش یک UserController داخلی میان خود و نود outbound قرار میدهد.
جایگذاری دستی فقط برای chainهای پیشرفته و سفارشی است که نود قبلی یک user handle معتبر ثبت میکند:
CustomAuthNode -> UserController -> TcpConnector
lineهایی که user handle معتبر ندارند بدون اعمال محدودیت عبور میکنند.
بعضی نودها user را فقط بعد از باز شدن line authenticate میکنند و سمت next آنها packet line است، یعنی یک line برای
هر worker، نه یک line جدا برای هر user. به همین دلیل نمیتوانند مثل Socks5Server یا TrojanServer یک
UserController بعد از خودشان قرار دهند. مثال اصلی این حالت، WireGuardDevice در حالت database-backed peer
authentication است؛ در این مدل UserController روی سمتی قرار میگیرد که هنوز per-peer line دارد. بسته به اینکه کدام
adapter سر chain باشد، آن per-peer line میتواند از هر کدام از دو سمت UserController شروع شود:
UdpStatelessSocket -> UserController -> WireGuardDevice -> TunDevice (line starts from prev)
TunDevice -> WireGuardDevice -> UserController -> UdpStatelessSocket (line starts from next)
در هر دو چیدمان، per-peer line ابتدا بدون مدیریت از UserController عبور میکند، چون هنوز user روی آن ثبت نشده است.
پس از احراز هویت peer در WireGuardDevice، user با lineAddUser() به line اضافه میشود و سپس
Programmatic Promotion API همان line را در همان لحظه زیر پوشش محدودیتهای user میبرد.
UserController در هر دو جهت کار میکند: چه line از prev با upstream Init آغاز شود و چه از next با downstream
Init. مبدأ هر line ذخیره میشود تا upload و download در جهت درست محاسبه شوند؛ بخش
Direction Awareness را ببینید.
نمونه تنظیمات
{
"name": "user-controller",
"type": "UserController",
"settings": {
"auth-client-node-name": "auth-client",
"sweep-interval-ms": 1000,
"verbose": false
},
"next": "outbound"
}
auth-client باید نام یک AuthenticationClient در همان config باشد. UserController تنها به آن نود اشاره میکند و auth client تازهای نمیسازد.
فیلدهای اجباری
| فیلد | نوع | توضیح |
|---|---|---|
type | string | باید UserController باشد. |
settings | object | باید object غیرخالی باشد. |
settings.auth-client-node-name | string | نام یک AuthenticationClient موجود. نباید به خود UserController اشاره کند. |
تنظیمات اختیاری
| گزینه | پیشفرض | توضیح |
|---|---|---|
sweep-interval-ms | 1000 | فاصله بررسی lineهای مدیریتشده روی هر worker، حتی در حالت idle؛ باید >= 1 باشد. |
verbose | false | جزئیات بیشتری در warning مربوط به رد یا بسته شدن lineهای مدیریتشده ثبت میکند. |
limitهایی که enforce میشود
UserController محدودیتهای user را از جدول محلی AuthenticationClient میخواند.
| محدوده | رفتار |
|---|---|
enabled | user غیرفعال نمیتواند line managed جدید باز کند و lineهای موجودش بسته میشوند. |
| expiry زمانی | user منقضیشده نمیتواند line مدیریتشده جدید باز کند و lineهای موجود او بسته میشوند. |
traffic.up, traffic.down, traffic.total | byteهای payload به counterهای تجمعی اضافه میشوند؛ user over-quota رد یا بسته خواهد شد. |
connections-out | حداکثر تعداد lineهای outbound همزمان برای user. |
ips | حداکثر تعداد source IPهای همزمان برای user. استفاده دوباره از IP قبلاً شمردهشده مجاز است. |
این نود connections-in را اعمال نمیکند. محدودیت سرعت bandwidth نیز وظیفه آن نیست؛ برای محدود کردن bytes-per-second از SpeedLimit استفاده کنید.
نبودن یک limit یا مقدار صفر آن یعنی فیلد مربوطه نامحدود است.
Direction Awareness
UserController جهت شروع هر line را ذخیره میکند و با همان اطلاعات ترافیک را به counterهای upload یا download نسبت
میدهد. «Upload» ترافیکی است که از user به سمت دیگر مسیر میرود و «download» ترافیکی است که به user برمیگردد. user همیشه
در سمتی قرار دارد که line از آن آغاز شده است.
| Line origin | Initiated by | Upstream payload counts as | Downstream payload counts as |
|---|---|---|---|
From prev (upstream Init) | یک head/prev adapter، مثلاً UdpStatelessSocket در ابتدای chain | upload | download |
From next (downstream Init) | یک tail/next adapter، مثلاً UdpStatelessSocket در انتهای chain | download | upload |
ردیف اول حالت رایج در Socks5Server، TrojanServer و VlessServer است. ردیف دوم زمانی پیش میآید که adapter سمت user
در انتهای chain باشد؛ مانند TunDevice -> WireGuardDevice -> UserController -> UdpStatelessSocket. در این چیدمان packetهای
user از next وارد میشوند، بنابراین downstream payload همان upload کاربر است. quota در هر دو حالت به یک شکل اعمال میشود.
جریان اجرا
در Init از هر سمت (upstream Init برای lineای که از prev شروع شده، downstream Init برای lineای که از next
شروع شده):
- line state آماده و جهت شروع line، از
prevیاnext، ذخیره میشود - user handle فعلی line خوانده میشود
- lineهای anonymous یا دارای handle نامعتبر بدون اعمال محدودیت عبور میکنند و میتوان بعداً آنها را promote کرد
- lineهای authenticated از طریق
AuthenticationClientبررسی میشوند - برای lineهای پذیرفتهشده connection/IP slot رزرو میشود و line در sweep ثبت خواهد شد
- line ردشده در همان سمتی که آن را شروع کرده finish میشود و سمت مقابل راه نمیافتد
برای هر payload، upload یا download با توجه به مبدأ line محاسبه میشود (بخش Direction Awareness را ببینید). اگر نتیجه accounting بستن user باشد، buffer بازیافت و هر دو جهت finish میشوند.
در Finish، نود accounting state را آزاد و state خودش را پاک میکند؛ سپس طبق قواعد directional finish، Finish را فقط به جهت مقابل میفرستد.
Programmatic Promotion API
بعضی نودها user را پس از باز شدن line authenticate میکنند؛ نمونههای آن در بخش جایگاه رایج آمده است. UserController برای این نودها تابع زیر را در interface.h عمومی در دسترس میگذارد:
WW_EXPORT user_admission_result_t usercontrollerTunnelTryManageLine(tunnel_t *t, line_t *l);
این تابع همان admission مسیر upstream Init را در لحظه اجرا میکند: همه limitها بررسی میشوند و در صورت پذیرش،
connection slot و IP slot رزرو خواهند شد. نودی که از آن استفاده میکند باید:
- pointer مربوط به instance نود
UserControllerرا نگه دارد؛ با همان الگویی کهSocks5Serverبرای ساختUserControllerداخلی خود باnodeUserControllerGet()وcreateHandleبه کار میبرد. - peer را authenticate و user را با
lineAddUser()روی line ثبت کند. - تابع
usercontrollerTunnelTryManageLine(user_controller_instance, line)را فراخوانی کند.
مقدارهای برگشتی:
| Result | معنی |
|---|---|
kUserAdmissionOk | line اکنون managed شده، یا از قبل managed بوده و call idempotent بوده، یا user ندارد و بهصورت unmanaged passthrough رها شده است. ادامه دادن مجاز است. |
هر مقدار دیگر kUserAdmission* | user پذیرفته نشده است؛ مثلاً disabled یا expired است، quota را رد کرده یا به connection/IP limit رسیده است. line همچنان بدون مدیریت و بدون تغییر میماند. |
قرارداد استفاده:
- اگر نتیجه non-OK باشد، این تابع نه
Finishمیفرستد و نه line state را از بین میبرد. رسیدگی به اتصال ردشده بر عهده caller است؛ caller باید خودش line را ببندد یا کنار بگذارد و نباید آن را پذیرفتهشده فرض کند. فراخوانی ناموفق هیچ slotی رزرو نمیکند، بنابراین تلاش دوباره در آینده امن است. - تابع را روی worker مالک line فراخوانی کنید، یعنی زمانی که
lineGetWID(l) == getWID()است. line باید یک normal line باشد که پیشتر از همین instance نودUserControllerعبور کرده است؛ این API برای packet line نیست. - وقتی line با این روش تحت مدیریت قرار گرفت، traffic accounting، idle sweep و آزاد شدن slotها هنگام
Finishدرست مانند lineای عمل میکنند که درInitپذیرفته شده است؛ caller به teardown اضافهای نیاز ندارد.
نمونه کد درون نود authenticating، پس از handshake موفق روی line:
lineAddUser(line, &resolved_handle, username, password);
user_admission_result_t admission = usercontrollerTunnelTryManageLine(ts->user_controller_tunnel, line);
if (admission != kUserAdmissionOk)
{
// limits exceeded (for example IP limit): reject this peer / close the line here.
return;
}
// admitted: continue bringing the peer up.
Sweep Timer
هر worker یک sweep timer دورهای دارد. timer، lineهای مدیریتشده را بررسی میکند تا user غیرفعال، منقضی، حذفشده یا over-quota حتی بدون payload جدید هم قطع شود.
sweep registry تا زمان مدیریت شدن lineها reference آنها را نگه میدارد. با بسته شدن line یا توقف worker، referenceها آزاد میشوند.
نکتههای عملیاتی
- جایگذاری دستی نیازمند node قبلی است که
lineAddUser()را با user handle معتبر صدا بزند. Socks5Server،TrojanServerوVlessServerدر authenticated mode این نود را داخلی میسازند؛ در آن حالت یکUserControllerدیگر مستقیم بعد از آنها قرار ندهید.UserControllerهیچوقت config user مثل password، enabled state یا limitها را نمینویسد.- live connection/IP counters state محلی پردازش هستند و از طریق helperهای
AuthenticationClientنگهداری میشوند. - cumulative traffic به stats عادی user اضافه میشود.
- این نود stream-style support node است، نه packet tunnel.
مشخصات نود
| ویژگی | مقدار |
|---|---|
| مخاطب | کاربران پیشرفته و ترکیب داخلی serverهای authenticated |
| جایگاه | Middle stream support node |
نیاز به AuthenticationClient | بله |
| Layer group | Anything to anything |
| Per-line state | دارد |
| ایجاد line | خیر |
| نابود کردن line | خیر |
| تغییر payload | خیر |
| Required left padding | 0 |