HeaderServer
HeaderServer در سمت سرور، headerهای کوچکِ metadata اتصال را میخواند. این
نود سه کار مختلف انجام میدهد:
- header دوبایتی port مخصوص WaterWall را از
HeaderClientمیخواند و درdest_context->portمینویسد. - HAProxy PROXY protocol v1 یا v2 را میخواند و IPv4 و port واقعی کلاینت را در
src_contextمینویسد. - هنگام
Initدر upstream یک destination port ثابت تنظیم میکند.
این نود یک تونل عادی stream در لایه ۴ است. Socket باز نمیکند، packet line
نمیسازد و line_t ایجاد نمیکند.
جایگاه رایج
Metadata مربوط به destination port در WaterWall:
client side: TcpListener -> HeaderClient -> TcpConnector
server side: TcpListener -> HeaderServer -> TcpConnector
Metadata مربوط به source از طریق PROXY protocol و از فرستنده قابل اعتماد:
trusted PROXY sender -> TcpListener -> HeaderServer -> TcpConnector
destination port ثابت:
TcpListener -> HeaderServer -> TcpConnector
حالت PROXY را تنها پس از یک فرستنده قابل اعتماد قرار دهید. اگر کلاینت اینترنتی اینترنتی مستقیم به این نود برسد، میتواند 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"
}
خواندن source IP و port از PROXY protocol
اگر peer واقعی قبلی HAProxy PROXY protocol میفرستد و نودهای بعدی 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 مینویسد، سپس نود
بعدی upstream را مقداردهی اولیه میکند و payload باقیمانده پس از header را
میفرستد.
اجبار به یک backend port
وقتی همه lineها باید یک destination port ثابت داشته باشند و نباید هیچ headerی مصرف شود، از override عددی استفاده کنید.
{
"name": "force-https",
"type": "HeaderServer",
"settings": {
"override": 443
},
"next": "connector"
}
پیش از حالت عددی، HeaderClient headerنویس قرار ندهید؛ مگر اینکه عمداً بخواهید
آن header بهعنوان application payload باقی بماند.
فیلدهای لازم
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه و یکتای نود. |
type | string | باید دقیقاً "HeaderServer" باشد. |
settings | object | اجباری است و باید override داشته باشد. |
next | string | نود stream بعدی در لایه ۴ که line مسیریابیشده را میگیرد. |
فیلد لازم داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
override | number یا string | مشخص میکند کدام فیلد جایگزین شود و، در صورت وجود، کدام header خوانده شود. |
این نود باید هم نود قبلی داشته باشد و هم next. برای ابتدا یا انتهای chain نیست.
settings.override
مقدارهای قابل قبول:
| مقدار | معنی |
|---|---|
"dest_context->port" | header دو بایتی upstream مخصوص WaterWall خوانده میشود و dest_ctx.port تنظیم میشود. |
"line->dest_ctx->port" | نام مستعار سازگار با "dest_context->port". |
"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 رد میشوند تا رفتار محافظهکارانه قدیمی این تونل
حفظ شود.
حالت 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 تنظیمشده بافر میشود. |
| upstream payload بعد از header | به next فرستاده میشود. |
| upstream pause/resume قبل از header | نادیده گرفته میشود؛ سمت next هنوز مقداردهی اولیه نشده است. |
| upstream pause/resume بعد از header | به next فرستاده میشود. |
downstream Est / pause / resume قبل از header | نادیده گرفته میشود. |
| downstream payload قبل از header | بافرش برای استفاده مجدد برگردانده و سمت قبلی بسته میشود. |
| downstream callbacks بعد از header | به نود قبلی فرستاده میشود. |
در حالت مقدار ثابت، line با Init در upstream برقرار میشود؛ بنابراین callbackهای
upstream و downstream به شکل معمول و در جهت خود عبور داده میشوند.
callbackهای UpStreamEst و DownStreamInit برای این نود غیرفعال هستند.
Finish
با دریافت Finish در upstream، HeaderServer state محلی را از بین میبرد. Finish
تنها زمانی به next فرستاده میشود که خواندن header مسیریابی کامل شده و سمت next
قبلاً مقداردهی اولیه شده باشد.
با دریافت Finish در downstream، state محلی از بین میرود و finish به نود قبلی
فرستاده میشود.
در صورت خطای پروتکل، state محلی از بین میرود. اگر next قبلاً مقداردهی اولیه شده
باشد آن سمت بسته میشود و سپس سمت قبلی نیز بسته خواهد شد.
چون این تونل سازنده line نیست، lineDestroy() را فراخوانی نمیکند.
محدودیتها و Padding
| مقدار | اندازه |
|---|---|
| WaterWall port header | 2 bytes |
| حداقل 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"استفاده کنید. - حالت PROXY source-fields را تنها با یک فرستنده قابل اعتماد PROXY به کار ببرید.
- حالت PROXY source-fields فعلاً فقط IPv4 را پشتیبانی میکند.
- حالت PROXY source-fields فقط
src_ctxرا بهروز میکند وdest_ctxرا تغییر نمیدهد. - تا پیش از دریافت header،
HeaderServerنود بعدی را مقداردهی اولیه نکرده است. - نباید بهعنوان state مشترک packet-line استفاده شود.