HeaderServer
state مسیر ادامه پیش از فراخوانی Init node بعدی ثبت میشود؛ بنابراین Est همگام transport بلافاصله، حتی در صورت تکرار، منتقل میشود. ورودی re-entrant از Init، Est یا replay اولیه پشت بایتهای قدیمیتر میماند. صف موقت ترتیب حداکثر ۲ MiB و ۱٬۰۲۴ buffer دارد و در همان dispatch همگام تخلیه میشود؛ مسیر عادی مستقیم صفی نگه نمیدارد. سرریز، line از نوع borrowed را از طریق owner میبندد.
پس از بازگشت Init بعدی و پایان replay صف اولیه، node همهٔ state محلی را آزاد و صفر میکند و برای همیشه روی آن line از مسیر callbackها کنار میرود. در حالت port ثابت، این bypass بلافاصله پس از تنظیم port و پیش از ارسال Init انجام میشود. پس از مقداردهی اولیهٔ سمت بعدی، Pause/Resume شفاف و تکرارپذیر هستند و replay اولیه را متوقف نمیکنند.
HeaderServer در سمت سرور، headerهای کوچکِ metadata اتصال را میخواند. این
node چهار کار مختلف انجام میدهد:
- header دوبایتی port مخصوص WaterWall را از
HeaderClientمیخواند و درdest_context->portمینویسد. - header تکبایتی TCP/UDP مخصوص WaterWall را میخواند و فقط
dest_context->protocolرا جایگزین میکند. - HAProxy PROXY protocol v1 یا v2 را میخواند و IPv4 و port واقعی کلاینت را در
src_contextمینویسد. - هنگام
Initدر upstream یک destination port ثابت تنظیم میکند.
این node یک تونل عادی stream در لایه ۴ است. socket باز نمیکند، packet line
نمیسازد و line_t ایجاد نمیکند.
جایگاه رایج
metadata مربوط به destination port در WaterWall:
client side: TcpListener -> HeaderClient -> TcpConnector
server side: TcpListener -> HeaderServer -> TcpConnector
metadata مبدأ از طریق PROXY protocol، با فرستندهٔ قابل اعتماد:
trusted PROXY sender -> TcpListener -> HeaderServer -> TcpConnector
destination port ثابت:
TcpListener -> HeaderServer -> TcpConnector
حالت PROXY را تنها پس از یک فرستنده قابل اعتماد قرار دهید. اگر کلاینت اینترنتی مستقیم به این node برسد، میتواند header پروتکل PROXY را جعل کند؛ مگر اینکه proxy یا transport خصوصی جلوی آن را گرفته باشد.
مثالهای کاربردی
برگرداندن port اصلی listener از HeaderClient
این حالت جفت کلاسیک WaterWall است. instance اول روی چند port عمومی گوش میدهد،
اما همه ترافیک را به یک port میانی میفرستد. سمت relay، port اصلی اتصال را قبل از انتخاب backend توسط TcpConnector برمیگرداند.
{
"name": "header-server",
"type": "HeaderServer",
"settings": {
"override": "dest_context->port"
},
"next": "connector"
}
HeaderClient سمت مقابل معمولاً چنین تنظیمی دارد:
{
"name": "header-client",
"type": "HeaderClient",
"settings": {
"data": "src_context->port"
},
"next": "relay-connector"
}
بازیابی تنها هویت TCP/UDP
اگر فرستنده فقط پروتکل را میفرستد و آدرس و port مقصد از قبل مشخص هستند، از این حالت استفاده کنید:
{
"name": "protocol-reader",
"type": "HeaderServer",
"settings": {
"override": "dest_context->protocol"
},
"next": "router-or-connector"
}
برای transport مقصد مورد نظر فرستنده، آن را با HeaderClient.data = "dest_context->protocol" جفت کنید؛ برای transport مبدأ پذیرفتهشده، از "src_context->protocol" استفاده کنید. سرور پیش از مقداردهی اولیهٔ next نشانگر را اعمال میکند، بدون اینکه آدرس، port یا metadata مبدأ را تغییر دهد.
برای حمل UDP روی TCP همچنان به framing جداگانهٔ datagram، مانند جفت UdpOverTcp، نیاز دارید.
خواندن source IP و port از PROXY protocol
اگر peer واقعی قبلی HAProxy PROXY protocol میفرستد و nodeهای بعدی WaterWall
باید کلاینت اصلی را در line->routing_context.src_ctx ببینند، از این حالت
استفاده کنید.
{
"name": "proxy-source-reader",
"type": "HeaderServer",
"settings": {
"override": "proxy-protocol->source-fields"
},
"next": "router-or-connector"
}
فرستنده میتواند HAProxy، nginx، Envoy یا HeaderClient با یکی از مقادیر
"proxy-protocol"، "proxy-protocol-v1" یا "proxy-protocol-v2" باشد.
نمونه فرستنده در WaterWall:
{
"name": "proxy-header",
"type": "HeaderClient",
"settings": {
"data": "proxy-protocol",
"frontend-ipv4": "203.0.113.10"
},
"next": "relay-connector"
}
در این حالت، HeaderServer تنها headerهای IPv4 TCP مربوط به PROXY را میپذیرد.
header را میخواند، source IP و source port را در src_ctx مینویسد، سپس node
بعدی upstream را مقداردهی اولیه میکند و payload باقیمانده پس از header را
میفرستد.
اجبار به یک backend port
وقتی همه lineها باید یک destination port ثابت داشته باشند و نباید هیچ headerای مصرف شود، از override عددی استفاده کنید.
{
"name": "force-https",
"type": "HeaderServer",
"settings": {
"override": 443
},
"next": "connector"
}
در حالت عددی، پیش از این node یک HeaderClient با قابلیت افزودن header قرار ندهید؛ مگر اینکه عمداً بخواهید
آن header بهعنوان application payload باقی بماند.
فیلدهای لازم
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه و یکتای node. |
type | string | باید دقیقاً "HeaderServer" باشد. |
settings | object | اجباری است و باید override داشته باشد. |
next | string | node stream بعدی در لایه ۴ که line مسیریابیشده را میگیرد. |
فیلد لازم داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
override | number یا string | مشخص میکند کدام فیلد جایگزین شود و، در صورت وجود، کدام header خوانده شود. |
این node باید هم node قبلی داشته باشد و هم next. برای ابتدا یا انتهای chain نیست.
settings.override
مقدارهای قابل قبول:
| مقدار | معنی |
|---|---|
"dest_context->port" | header دو بایتی upstream مخصوص WaterWall خوانده میشود و dest_ctx.port تنظیم میشود. |
"line->dest_ctx->port" | نام مستعار سازگار با "dest_context->port". |
"dest_context->protocol" | header تکبایتی TCP/UDP مخصوص WaterWall خوانده میشود و فقط dest_ctx.protocol جایگزین میشود. |
"line->dest_ctx->protocol" | نام مستعار "dest_context->protocol". |
"proxy-protocol->source-fields" | PROXY protocol v1 یا v2 خوانده میشود و src_ctx.ip و src_ctx.port تنظیم میشوند. |
1 تا 65535 | این destination port ثابت در upstream Init تنظیم میشود. |
اگر override وجود نداشته باشد، رشتهای ناشناخته باشد یا عددی بیرون بازه 1
تا 65535 داشته باشد، راهاندازی شکست میخورد.
حالت WaterWall Port Header
وقتی override برابر "dest_context->port" یا "line->dest_ctx->port" باشد،
HeaderServer منتظر دو بایت نخست upstream میماند. این دو بایت بهعنوان port
با ترتیب big-endian decode میشوند و در فیلد زیر نوشته میشوند:
line->routing_context.dest_ctx.port
Init در upstream عمداً تا زمان decode شدن header به تأخیر میافتد. بایتهای
payload در buffer_stream_t نگه داشته میشوند، بنابراین header دوبایتی میتواند
در چند callbackِ payload تکهتکه دریافت شود.
portهای decodeشده کمتر از 10 رد میشوند تا رفتار محافظهکارانه قدیمی این تونل
حفظ شود.
حالت WaterWall Protocol Header
وقتی override برابر "dest_context->protocol" یا "line->dest_ctx->protocol" باشد، HeaderServer منتظر یک بایت upstream میماند. مقدار 6 را بهعنوان TCP و 17 را بهعنوان UDP میخواند و فقط فیلد زیر را جایگزین میکند:
line->routing_context.dest_ctx.protocol
این نشانگر فقط یک بار برای هر line عادی لایه ۴ خوانده میشود. همهٔ metadata مبدأ و آدرس و port مقصد حفظ میشوند. سرور پیش از فراخوانی Init بعدی پروتکل را اعمال میکند و سپس payload باقیمانده را بهترتیب عبور میدهد. callbackهای payload خالی header را کامل نمیکنند. هر مقدار بایت دیگری، line را بدون مقداردهی اولیهٔ next میبندد.
این header فقط metadata داخلی مسیریابی است و آدرس IP، port یا فیلدهای PROXY protocol ندارد. مرز datagramهای UDP را نیز encode نمیکند.
حالت PROXY Protocol Source-Fields
اگر override برابر "proxy-protocol->source-fields" باشد، نخستین بایتهای
upstream باید یک header معتبر HAProxy PROXY protocol v1 یا v2 باشند. اگر header
وجود نداشته باشد، malformed باشد، از نوع IPv4 نباشد یا فیلدهای source را نداشته
باشد، HeaderServer خطایی در لاگ ثبت میکند و اتصال را میبندد.
headerهای قابل قبول:
| header | فرم قابل قبول |
|---|---|
| PROXY v1 | PROXY TCP4 <source-ip> <dest-ip> <source-port> <dest-port>\r\n |
| PROXY v2 | version 2، command برابر PROXY، family/transport برابر TCP4 |
موارد رد شده:
- PROXY v1
UNKNOWN - PROXY v1
TCP6 - PROXY v2
LOCAL - PROXY v2
UNSPEC،TCP6،UDP4،UDP6یا هر family پشتیبانینشده - headerهای malformed یا بیش از حد بزرگ
فقط این فیلدها اعمال میشوند:
line->routing_context.src_ctx.ip_address = PROXY source IPv4
line->routing_context.src_ctx.port = PROXY source port
line->routing_context.src_ctx.protocol = TCP
فیلدهای destination، TLVها و دیگر فیلدهای PROXY خوانده میشوند، اما نادیده گرفته
میشوند. IPv6 در این mode پشتیبانی نمیشود. peer_source_port بازنویسی نمیشود.
حداکثر طول PROXY v1 برابر 108 بایت است. PROXY v2 تمام record، از جمله TLVها، را میخواند ولی وجود address block ثابت TCP4 را لازم دارد.
حالت Constant Override
اگر override عدد باشد، HeaderServer هیچ headerای از payload نمیخواند و چیزی
را حذف نمیکند.
در upstream Init این مقدار تنظیم میشود:
line->routing_context.dest_ctx.port = configured port
سپس Init بیدرنگ در upstream به next فرستاده میشود و تمام payloadهای upstream
بدون تغییر عبور میکنند.
رفتار جهتها
| جهت | رفتار در حالتهای header تأخیری |
|---|---|
upstream Init | state محلی مقداردهی اولیه میشود، اما ارسال init به next تا decode شدن header تنظیمشده عقب میافتد. |
| upstream payload قبل از header | تا کامل شدن header تنظیمشده buffer میشود. |
| upstream payload بعد از header | به next فرستاده میشود. |
| upstream pause/resume قبل از header | نادیده گرفته میشود؛ سمت next هنوز مقداردهی اولیه نشده است. |
| upstream pause/resume بعد از header | به next فرستاده میشود. |
downstream Est / pause / resume قبل از header | نادیده گرفته میشود. |
| downstream payload قبل از header | bufferش برای استفاده مجدد برگردانده و سمت قبلی بسته میشود. |
| downstream callbacks بعد از header | به node قبلی فرستاده میشود. |
در حالت مقدار ثابت، line با Init در upstream برقرار میشود؛ بنابراین callbackهای
upstream و downstream به شکل معمول و در جهت خود عبور داده میشوند.
callbackهای UpStreamEst و DownStreamInit برای این node غیرفعال هستند.
Finish
پس از bypass، Finish مستقیماً به node فعال بعدی در همان جهت میرسد. پاکسازی زیر تا پیش از پایان مسیریابی header و replay اولیه اعمال میشود.
با دریافت Finish در upstream، HeaderServer state محلی را از بین میبرد. Finish
تنها زمانی به next فرستاده میشود که خواندن header مسیریابی کامل شده و سمت next
قبلاً مقداردهی اولیه شده باشد.
با دریافت Finish در downstream، state محلی از بین میرود و finish به node قبلی
فرستاده میشود.
در صورت خطای پروتکل، state محلی از بین میرود. اگر next قبلاً مقداردهی اولیه شده
باشد آن سمت بسته میشود و سپس سمت قبلی نیز بسته خواهد شد.
چون این تونل سازنده line نیست، lineDestroy() را فراخوانی نمیکند.
محدودیتها و Padding
| مقدار | اندازه |
|---|---|
| WaterWall port header | 2 bytes |
| WaterWall protocol header | 1 byte |
| حداقل port قابل قبول از WaterWall header | 10 |
| PROXY protocol v1 header | حداکثر 108 bytes |
| PROXY protocol v2 base header | 16 bytes |
| address block لازم برای PROXY protocol v2 TCP4 | 12 bytes |
required_padding_left | 0 bytes |
سرور فقط header را میخواند. بایت جدیدی prepend نمیکند و به padding چپ نیاز ندارد.
نکتهها
- حالت port-header در WaterWall را همراه با
HeaderClient.data = "src_context->port"استفاده کنید. - حالت protocol-header را با
HeaderClient.data = "src_context->protocol"یا"dest_context->protocol"جفت کنید؛ حمل UDP روی TCP به framing جداگانه نیاز دارد. - حالت PROXY source-fields را تنها با یک فرستنده قابل اعتماد PROXY به کار ببرید.
- حالت PROXY source-fields فعلاً فقط IPv4 را پشتیبانی میکند.
- حالت PROXY source-fields فقط
src_ctxرا بهروز میکند وdest_ctxرا تغییر نمیدهد. - تا پیش از دریافت header،
HeaderServernode بعدی را مقداردهی اولیه نکرده است. - نباید بهعنوان state مشترک packet-line استفاده شود.
متادیتای نود
metadata برگرفته از کد منبع:
| ویژگی | مقدار |
|---|---|
| node flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 0 bytes |
پشتیبانی از splice
HeaderServer پرچم kNodeFlagSupportsSplice را دارد. هنگام انتظار برای header
پورت، پروتکل TCP/UDP یا PROXY protocol، کل payload دریافتی را با sbufEnsureOrdinary() به حافظهٔ
عادی تبدیل میکند و سپس به BufferStream میدهد. بایتهای application همراه header
حفظ میشوند. پس از تکمیل header، هر دو جهت و صف محدود reentry، bufferهای splice
را بدون تبدیل عبور میدهند. حالت پورت ثابت به خواندن payload نیاز ندارد.
برای parsing موقت، ترجیح خواندن عادی تنظیم نمیشود؛ این ترجیح تا پایان line باقی میماند و خواندن splice بعدی را هم غیرفعال میکند.