SpeedLimit
SpeedLimit یک tunnel عبوری است که نرخ عبور بایتهای payload را با token bucket
محدود میکند. این node داده protocol را تغییر نمیدهد، header اضافه یا حذف
نمیکند و destination context را تغییر نمیدهد؛ فقط تعیین میکند payload اکنون
عبور کند، منتظر بماند یا کنار گذاشته شود.
هر دو جهت از همان bucket استفاده میکنند. برای نمونه، اگر سرعت یک bucket برابر
1 MB/s باشد، مجموع upload و download آن bucket همین ظرفیت را با هم تقسیم
میکنند.
جایگاه رایج
برای stream:
TcpListener -> SpeedLimit -> TcpConnector
TcpListener -> TlsServer -> SpeedLimit -> TcpConnector
برای packet یا datagram:
UdpListener -> SpeedLimit -> UdpConnector
TunDevice -> SpeedLimit -> UdpStatelessSocket
برای ترافیک stream معمولاً حالت pause مناسبتر است تا داده اضافه منتظر بماند.
برای packet و datagram معمولاً drop بهتر است، چون packet قدیمی اغلب از packet
ازدسترفته هم کمارزشتر است.
نمونه stream
در این نمونه هر connection یک bucket جدا با سرعت 256 KiB/s دارد و داده اضافی تا پر شدن دوباره tokenها در صف میماند.
{
"name": "speed-limit",
"type": "SpeedLimit",
"settings": {
"kilo-bytes-per-sec": 256,
"limit-mode": "per-line",
"work-mode": "pause"
},
"next": "next-node"
}
نمونه limit مشترک
در این نمونه همه lineهای عبوری از این instance یک bucket مشترک 8 MiB/s دارند.
{
"name": "global-speed-limit",
"type": "SpeedLimit",
"settings": {
"mega-bytes-per-sec": 8,
"limit-mode": "all-lines",
"token-recharge-rate": 20,
"work-mode": "pause"
},
"next": "next-node"
}
نمونه packet drop
در این نمونه هر worker یک bucket مشترک 500000 بایت بر ثانیه دارد و chunkهایی که token کافی برایشان موجود نیست کنار گذاشته میشوند.
{
"name": "packet-speed-limit",
"type": "SpeedLimit",
"settings": {
"bytes-per-sec": 500000,
"limit-mode": "per-worker",
"work-mode": "drop"
},
"next": "next-node"
}
فیلدهای اجباری
settings الزامی است و باید objectی غیرخالی باشد.
دقیقاً یکی از فیلدهای سرعت باید تنظیم شود:
| فیلد | معنی |
|---|---|
bytes-per-sec | بایت بر ثانیه. |
kilo-bytes-per-sec | کیبیبایت بر ثانیه، ضرب در 1024. |
mega-bytes-per-sec | مبیبایت بر ثانیه، ضرب در 1024 * 1024. |
مقدار انتخابشده باید عددی بزرگتر از 0 باشد. تنظیم همزمان بیش از یک فیلد سرعت باعث شکست ساخت node میشود.
این فیلدها هم اجباری هستند:
| فیلد | مقدارهای مجاز |
|---|---|
limit-mode | per-line, per-connection, all-lines, all-connections, per-worker |
work-mode | pause, drop |
در parser فعلی این stringها به حروف کوچک و بزرگ حساساند.
فیلد اختیاری
| فیلد | پیشفرض | توضیح |
|---|---|---|
token-recharge-rate | 10 | فاصله refill شدن tokenها به میلیثانیه. باید بزرگتر از 0 باشد. |
token-recharge-rate مقدار پهنایباند را تعیین نمیکند؛ فقط فاصله زمانی افزودن token به bucket را مشخص میسازد.
برای مثال، با این تنظیم:
bytes-per-sec: 1000token-recharge-rate: 10
bucket تقریباً هر 10 ms به اندازه 10 byte token دریافت میکند. interval کوچکتر معمولاً خروجی روانتری میدهد و interval بزرگتر میتواند ترافیک را burstیتر کند.
Limit modeها
| mode | رفتار |
|---|---|
per-line / per-connection | هر line در WaterWall bucket خودش را دارد. دو line همزمان با 1 MB/s میتوانند در مجموع حدود 2 MB/s مصرف کنند. |
all-lines / all-connections | همه lineهای این tunnel instance یک bucket سراسری و atomic میان workerها دارند. با 1 MB/s، کل node همین ظرفیت را میان همه lineها تقسیم میکند. |
per-worker | هر worker bucket مشترک خودش را دارد. با 4 worker و 1 MB/s، سقف عملی کل میتواند حدود 4 MB/s شود. |
هر bucket در ابتدا پر است و ظرفیتی برابر حدود یک ثانیه از نرخ تنظیمشده دارد. در نتیجه ممکن است در شروع کار، نزدیک به یک ثانیه ترافیک را به شکل burst عبور دهد و سپس روی میانگین تعیینشده پایدار شود.
Work modeها
pause
pause برای ترافیک ترتیبی مانند TCP طراحی شده است.
وقتی payload در bucket جا نشود:
- buffer درون
SpeedLimitدر صف قرار میگیرد. - سمت source با callbackهای
PauseWaterWall متوقف میشود. - یک timer روی worker منتظر refill شدن tokenها میماند.
- داده صفشده به همان ترتیب ارسال میشود.
- با خالی شدن صف، ارسال در سمتی که بهصورت local متوقف شده بود از سر گرفته میشود.
اگر buffer صفشده از token موجود بزرگتر باشد، SpeedLimit میتواند بخش ابتدایی
آن را بفرستد و باقی را تا tick بعدی در صف نگه دارد. با این کار ترتیب بایتها
حفظ و نرخ میانگین کنترل میشود.
حالت pause bufferها را در حافظه نگه میدارد؛ بنابراین limit بسیار پایین، peer
کند یا نرخ ورودی بالا میتواند مصرف حافظه و latency را افزایش دهد.
drop
drop برای ترافیک packet و datagram طراحی شده است.
برای هر payload chunk:
- اگر bucket برای کل chunk به اندازه کافی token داشته باشد، chunk فرستاده میشود
- اگر token کافی نباشد، کل chunk کنار گذاشته میشود
در حالت drop، payload نه تکهتکه میشود و نه برای بعد در صف میماند. این رفتار معمولاً برای UDP و packet chain مناسبتر است، زیرا datagramهای قدیمی اغلب دیگر کاربردی ندارند.
جهتها و backpressure
محدودسازی payload و backpressure در هر دو جهت متقارن است:
- upstream و downstream از همان منطق bucket انتخابشده استفاده میکنند
- داده صفشده در upstream، سمت قبلی را متوقف میکند
- داده صفشده در downstream، سمت بعدی را متوقف میکند
- حالت
PauseوResumeخارجی بهخاطر سپرده میشود تاSpeedLimitسمتی را که هنوز از بیرون pause است به اشتباه resume نکند
در نتیجه حالت pause با backpressure معمول WaterWall هماهنگ میشود و صرفاً درون callback منتظر نمیماند.
پشتیبانی از splice و سقف صف
هر جهت یک FIFO جدا دارد که حداکثر 16 MiB payload، برابر 32 MiB charge ظرفیت buffer و 1,024 buffer را نگه میدارد. buffer خالی نیز charge و یک entry مصرف میکند. رد شدن admission، اتصال TCP مربوط را میبندد و هر دو صف را آزاد میکند؛ بایتهای TCP پذیرفتهشده بدون اطلاع حذف نمیشوند.
Pause از consumer تخلیه timer همان جهت را متوقف میکند. داده در صف باقی میماند و timer هنگام Pause مرتباً polling نمیکند. Resume تخلیه را دوباره زمانبندی میکند، اما source فقط پس از رفع همزمان فشار بیرونی و hold صف محلی Resume دریافت میکند. اگر ساختن یا فعالسازی دوبارهٔ timer رد شود، هر دو صف آزاد میشوند و اتصال مربوط از طریق owner بسته میشود. بسته شدن line از داخل callback نیز بهصورت ایمن مدیریت میشود.
این node فلگ kNodeFlagSupportsSplice دارد. عبور کامل buffer و نگهداری در صف،
pipe خصوصی را حفظ میکنند. grant جزئی در حالت pause با sbufMoveRangeTo()
دقیقاً همان تعداد بایت را به pipe مستقل منتقل میکند؛ اگر ایجاد pipe یا انتقال
کامل ممکن نباشد، خروجی به buffer معمولی تبدیل میشود. باقیمانده قدیمی پیش از
ورودی تودرتو قرار میگیرد. حساب token شامل prefix و body داخل pipe است. حالت
drop بدون خواندن بایتهای payload درباره کل buffer تصمیم میگیرد.
splice به پشتیبانی تمام nodeهای chain نهایی وابسته است. packet chainها و platformهای فاقد splice همچنان از buffer معمولی استفاده میکنند.
نکته packet-line
packet lineهای WaterWall، lineهای کمکی و محلی هر worker هستند که برای packetهای
زیادی دوباره به کار میروند. در packet chain، per-line یعنی bucket متصل به
packet line پایدار همان worker، نه یک bucket واقعی برای هر flow یا client. اگر limit
بر اساس worker میخواهید، per-worker را صریح انتخاب کنید. اگر یک cap مشترک بین همه workerها میخواهید، all-lines را
انتخاب کنید.
برای chainهای packet یا UDP-style معمولاً work-mode: "drop" انتخاب بهتری است، مگر اینکه عمداً delayed packet delivery و
backpressure بخواهید.
رفتار Finish
line state فقط هنگام Init در جهت upstream مقداردهی اولیه میشود. دریافت Init
در جهت downstream ممنوع است و با abortProgramNow(1) فرایند را خاتمه میدهد.
پس از مقداردهی اولیه در upstream، دریافت payload در downstream همچنان پشتیبانی میشود.
این node را بین Bridge و ReverseClient قرار ندهید؛ وقتی این دو node در چنین
توپولوژیای به هم متصل هستند، باید مستقیماً کنار یکدیگر باشند.
هنگام Finish، node state محلی را از
بین میبرد، timerهای تخلیه صف را لغو میکند، bufferهای صفشده را آزاد میکند و
سپس Finish WaterWall را در همان جهت میفرستد.
این tunnel چیزی به ابتدای payload اضافه نمیکند؛ بنابراین required_padding_left برابر 0 است.
اشتباههای رایج
- بیشتر از یک فیلد سرعت تنظیم نکنید.
- برای packet chainها از
pauseاستفاده نکنید مگر اینکه packetهای تاخیردار قابل قبول باشند. - از
per-workerانتظار یک سقف مشترک سراسری نداشته باشید؛ سقف عملی با تعداد workerها افزایش مییابد. - در packet chainها
per-lineرا per-client یا per-flow فرض نکنید. token-recharge-rateرا بسیار بزرگ نگذارید، مگر اینکه خروجی burstی برایتان قابل قبول باشد.
متادیتای node
متادیتای برگرفته از کد منبع:
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayerAnything |
layer_group_prev_node | kNodeLayerSameAsNext |
layer_group_next_node | kNodeLayerSameAsPrev |
required_padding_left | 0 بایت |