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

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 تازه‌ای نمی‌سازد.

فیلدهای اجباری

فیلدنوعتوضیح
typestringباید UserController باشد.
settingsobjectباید object غیرخالی باشد.
settings.auth-client-node-namestringنام یک AuthenticationClient موجود. نباید به خود UserController اشاره کند.

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

گزینهپیش‌فرضتوضیح
sweep-interval-ms1000فاصله بررسی lineهای مدیریت‌شده روی هر worker، حتی در حالت idle؛ باید >= 1 باشد.
verbosefalseجزئیات بیشتری در warning مربوط به رد یا بسته شدن lineهای مدیریت‌شده ثبت می‌کند.

limitهایی که enforce می‌شود

UserController محدودیت‌های user را از جدول محلی AuthenticationClient می‌خواند.

محدودهرفتار
enableduser غیرفعال نمی‌تواند line managed جدید باز کند و lineهای موجودش بسته می‌شوند.
expiry زمانیuser منقضی‌شده نمی‌تواند line مدیریت‌شده جدید باز کند و lineهای موجود او بسته می‌شوند.
traffic.up, traffic.down, traffic.totalbyteهای 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 originInitiated byUpstream payload counts asDownstream payload counts as
From prev (upstream Init)یک head/prev adapter، مثلاً UdpStatelessSocket در ابتدای chainuploaddownload
From next (downstream Init)یک tail/next adapter، مثلاً UdpStatelessSocket در انتهای chaindownloadupload

ردیف اول حالت رایج در 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 شروع شده):

  1. line state آماده و جهت شروع line، از prev یا next، ذخیره می‌شود
  2. user handle فعلی line خوانده می‌شود
  3. lineهای anonymous یا دارای handle نامعتبر بدون اعمال محدودیت عبور می‌کنند و می‌توان بعداً آن‌ها را promote کرد
  4. lineهای authenticated از طریق AuthenticationClient بررسی می‌شوند
  5. برای lineهای پذیرفته‌شده connection/IP slot رزرو می‌شود و line در sweep ثبت خواهد شد
  6. 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 رزرو خواهند شد. نودی که از آن استفاده می‌کند باید:

  1. pointer مربوط به instance نود UserController را نگه دارد؛ با همان الگویی که Socks5Server برای ساخت UserController داخلی خود با nodeUserControllerGet() و createHandle به کار می‌برد.
  2. peer را authenticate و user را با lineAddUser() روی line ثبت کند.
  3. تابع usercontrollerTunnelTryManageLine(user_controller_instance, line) را فراخوانی کند.

مقدارهای برگشتی:

Resultمعنی
kUserAdmissionOkline اکنون 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 groupAnything to anything
Per-line stateدارد
ایجاد lineخیر
نابود کردن lineخیر
تغییر payloadخیر
Required left padding0