HeaderClient
HeaderClient در ابتدای نخستین payloadِ upstream هر line عادی در لایه ۴، یک
header یکباره قرار میدهد.
این نود معمولاً یکی از دو کاربرد زیر را دارد:
- فرستادن metadata مسیریابی WaterWall به یک
HeaderServerجفتشده - افزودن header پروتکل HAProxy PROXY پیش از رسیدن ترافیک به گیرندهای که PROXY protocol را میشناسد
HeaderClient socket باز نمیکند و line نمیسازد. تنها نخستین payloadِ upstream را تغییر میدهد و در ادامه عمر line، داده را بدون تغییر عبور میدهد.
عملکرد
- برای انتخاب فرمت header یکباره به
settings.dataنیاز دارد. - با دریافت
Initدر upstream، state محلی line را مقداردهی میکند. Initرا بیدرنگ در جهت upstream بهnextمیفرستد.- فقط روی اولین upstream payload، header تنظیمشده را prepend میکند.
- از header قدیمی دوبایتی port در WaterWall پشتیبانی میکند.
- از headerهای IPv4-only مربوط به HAProxy PROXY protocol v1 و v2 با یک frontend IPv4 تنظیمشده پشتیبانی میکند.
- همه payloadهای بعدی upstream را بدون تغییر عبور میدهد.
- Payload و callbackهای
Est،PauseوResumeرا در downstream بدون تغییر عبور میدهد. - پیش از انتشار
Finishدر هر جهت، state محلی خود را از بین میبرد.
جایگاه رایج
Header داخلی WaterWall برای port:
TcpListener -> HeaderClient -> ... -> HeaderServer -> TcpConnector
Header پروتکل PROXY برای گیرنده راه دور سازگار با PROXY:
sender side: TcpListener -> HeaderClient -> TcpConnector
receiver side: HeaderServer/HAProxy/nginx/backend
در حالت پروتکل PROXY، peer واقعی بعدی باید انتظار HAProxy PROXY protocol داشته
باشد. این peer میتواند HeaderServer با settings.override = "proxy-protocol->source-fields" یا یک backend دیگر سازگار با PROXY باشد. این
حالت همچنین به settings.frontend-ipv4 نیاز دارد؛ این مقدار باید IPv4 مربوط به
frontendای باشد که کلاینتها به آن وصل میشوند، نه آدرس backend.
مثالهای کاربردی
حفظ port listener پذیرفتهشده در یک relay
این کاربرد کلاسیک HeaderClient در کنار HeaderServer است. یک instance سمت کلاینت روی چند port عمومی گوش میدهد، اما همه ترافیک را به یک port میانی روی instance دیگری از WaterWall میفرستد. سمت relay، port اصلی اتصال را در dest_context->port بازیابی میکند و سپس به service محلی متصل میشود.
سمت کلاینت:
[
{
"name": "public-listener",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": [80, 443, 8443]
},
"next": "header-client"
},
{
"name": "header-client",
"type": "HeaderClient",
"settings": {
"data": "src_context->port"
},
"next": "relay-connector"
},
{
"name": "relay-connector",
"type": "TcpConnector",
"settings": {
"address": "198.51.100.20",
"port": 9000
}
}
]
سمت relay:
[
{
"name": "relay-listener",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 9000
},
"next": "header-server"
},
{
"name": "header-server",
"type": "HeaderServer",
"settings": {
"override": "dest_context->port"
},
"next": "local-service"
},
{
"name": "local-service",
"type": "TcpConnector",
"settings": {
"address": "127.0.0.1",
"port": "dest_context->port"
}
}
]
با این چیدمان، اگر سمت کلاینت اتصال را روی port عمومی 443 بپذیرد، اتصال از طریق 198.51.100.20:9000 عبور میکند و سپس سمت relay به 127.0.0.1:443 وصل میشود.
اجبار همه ترافیک relay شده به یک backend port
اگر سمت relay منتظر header دوبایتی WaterWall است، اما مقدار باید ثابت باشد و از contextِ line کپی نشود، برای settings.data یک عدد بنویسید.
{
"name": "header-client",
"type": "HeaderClient",
"settings": {
"data": 8443
},
"next": "relay-connector"
}
در سمت مقابل، HeaderServer را طوری تنظیم کنید که این header را بخواند و در dest_context->port بنویسد؛ سپس TcpConnector از "dest_context->port" استفاده کند. در نتیجه هر line این کلاینت، port شماره 8443 را در backend انتخاب میکند.
ارسال PROXY protocol v2 به backend محلی
اگر peer واقعی بعدی HAProxy، nginx، Envoy یا service دیگری است که پیش از payload اصلی کلاینت انتظار PROXY protocol دارد، از "proxy-protocol" استفاده کنید.
[
{
"name": "public-listener",
"type": "TcpListener",
"settings": {
"address": "0.0.0.0",
"port": 443
},
"next": "proxy-header"
},
{
"name": "proxy-header",
"type": "HeaderClient",
"settings": {
"data": "proxy-protocol",
"frontend-ipv4": "203.0.113.10"
},
"next": "backend"
},
{
"name": "backend",
"type": "TcpConnector",
"settings": {
"address": "192.0.2.50",
"port": 443
}
}
]
"proxy-protocol" نام مستعار "proxy-protocol-v2" است. PROXY protocol v2 فشرده و binary است و اگر backend از آن پشتیبانی کند، گزینه پیشفرض بهتری است. frontend-ipv4 را روی IPv4ای بگذارید که کلاینت برای رسیدن به frontendِ WaterWall استفاده میکند؛ نه 0.0.0.0 و نه آدرس backend.
ارسال PROXY protocol v1 برای receiverهای قدیمی
بعضی گیرندههای قدیمی فقط فرمت متنی PROXY protocol را میپذیرند. در این حالت از "proxy-protocol-v1" استفاده کنید:
{
"name": "proxy-header",
"type": "HeaderClient",
"settings": {
"data": "proxy-protocol-v1",
"frontend-ipv4": "203.0.113.10"
},
"next": "backend"
}
گیرنده باید طوری تنظیم شود که انتظار PROXY protocol v1 داشته باشد. برای گیرندههای
WaterWall از HeaderServer با settings.override = "proxy-protocol->source-fields" استفاده کنید.
فیلدهای لازم
فیلدهای سطح اصلی:
| فیلد | نوع | توضیح |
|---|---|---|
name | string | نام دلخواه که باید در پیکربندی یکتا باشد. |
type | string | باید دقیقاً "HeaderClient" باشد. |
settings | object | اجباری است و باید data داشته باشد. |
next | string | نود stream بعدی در لایه ۴ که stream دارای header را میگیرد. |
فیلدهای لازم داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
data | number یا string | فرمت header و دادهای که در نخستین payloadِ upstream encode میشود. |
فیلدهای شرطی داخل settings:
| فیلد | نوع | توضیح |
|---|---|---|
frontend-ipv4 | string | اگر data برابر "proxy-protocol"، "proxy-protocol-v1" یا "proxy-protocol-v2" باشد، اجباری است. مقدار باید IPv4 مربوط به frontendای باشد که کلاینتها به آن متصل میشوند. |
HeaderClient باید نود قبلی هم داشته باشد. این نود برای ابتدا یا انتهای chain نیست.
settings.data
مقدارهای قابل قبول:
| مقدار | معنی |
|---|---|
1 تا 65535 | همین port ثابت را در header دوبایتی WaterWall encode میکند. |
"src_context->port" | مقدار line->routing_context.src_ctx.port را بهعنوان header دو بایتی WaterWall encode میکند. |
"line->src_ctx->port" | نام مستعار سازگار با "src_context->port". |
"proxy-protocol" | یک header از نوع PROXY protocol v2 تولید میکند. |
"proxy-protocol-v1" | یک header از نوع PROXY protocol v1 تولید میکند. |
"proxy-protocol-v2" | یک header از نوع PROXY protocol v2 تولید میکند. |
اگر data وجود نداشته باشد، رشتهای ناشناخته باشد یا عددی بیرون بازه 1 تا 65535 داشته باشد، راهاندازی شکست میخورد.
رفتار روی Wire
در حالت عددی یا source-port، نخستین payloadِ upstream از این شکل:
payload
به این حالت تبدیل میشود:
2-byte port header | payload
Header مربوط به port یک مقدار شانزدهبیتی big-endian است که فقط یک بار برای هر line فرستاده میشود. Payloadهای بعدی upstream دقیقاً بدون تغییر عبور میکنند.
در حالتهای PROXY protocol، HeaderClient یک header از نوع IPv4 TCP PROXY میسازد. Source IP از line->routing_context.src_ctx و destination IP از settings.frontend-ipv4 گرفته میشود. برای مقصد در headerِ PROXY از dest_ctx استفاده نمیشود، زیرا ممکن است TcpConnector پیش از ارسال نخستین payload، dest_ctx را به backend مقصد اتصال تغییر داده باشد.
portهای انتخابشده:
- source port: اگر
routing_context.peer_source_portموجود باشد همان مقدار، وگرنهsrc_ctx.port - destination port: اگر
routing_context.local_listener_portموجود باشد همان مقدار، وگرنهsrc_ctx.port
اگر هنگام رسیدن نخستین payloadِ upstream، آدرس مبدأ از نوع IPv4 نباشد، نسخه v1 مقدار PROXY UNKNOWN و نسخه v2 یک header از نوع LOCAL/UNSPEC تولید میکند. Headerهای IPv6 و TLVهای PROXY protocol تولید نمیشوند.
حتی اگر نخستین payloadِ upstream خالی باشد، همان callbackِ نخست header تنظیمشده را میفرستد.
نکتههای Routing Context
HeaderClient تنها اطلاعاتی را serialize میکند که از قبل روی line وجود دارند. Domain را resolve نمیکند و آدرس مفقود را نمیسازد.
در حالت قدیمی header دوبایتی WaterWall، روند معمول چنین است:
TcpListener، port محلی listener را که اتصال روی آن پذیرفته شده درsrc_ctx.portقرار میدهد.HeaderClientمیتواند آن مقدار را در header دوبایتی کپی کند.HeaderServerمیتواند همان مقدار را در سمت دیگر درdest_ctx.portبازسازی کند.
در حالت پروتکل PROXY، context آدرس مبدأ باید به socket address از نوع IPv4 تبدیلشدنی باشد. آدرس مقصد در headerِ PROXY همان frontend-ipv4 تنظیمشده است، نه dest_ctx. این رفتار عمدی است: در chainهای رایجی مانند TcpListener -> HeaderClient -> TcpConnector، معمولاً dest_ctx آدرس backend انتخابشده توسط TcpConnector را نشان میدهد، نه آدرس frontendای که کلاینت به آن رسیده است.
استفاده با HeaderServer
HeaderClient و HeaderServer را در یکی از حالتهای سازگار با هم به کار ببرید.
برای header دو بایتی WaterWall:
{
"name": "header-server",
"type": "HeaderServer",
"settings": {
"override": "dest_context->port"
},
"next": "connector"
}
برای برگرداندن source fields از PROXY protocol:
{
"name": "header-server",
"type": "HeaderServer",
"settings": {
"override": "proxy-protocol->source-fields"
},
"next": "connector"
}
این حالت را با مقدار HeaderClient.data برابر "proxy-protocol"،
"proxy-protocol-v1" یا "proxy-protocol-v2" جفت کنید. reader سمت server برای
PROXY فعلاً فقط IPv4 را پشتیبانی میکند و تنها src_ctx.ip و src_ctx.port را اعمال میکند.
اگر HeaderServer.settings.override عدد باشد، سرور هنگام Init در upstream یک destination port ثابت تنظیم میکند و headerای از داده نمیخواند. در این حالت، اگر پیش از آن HeaderClient قرار دهید، header دوبایتی بخشی از payload برنامه باقی میماند.
رفتار جهتها
| جهت | رفتار |
|---|---|
upstream Init | state محلی را مقداردهی اولیه میکند و callback را به next میفرستد. |
| اولین upstream payload | header تنظیمشده را prepend میکند و به next میفرستد. |
| payloadهای upstream بعدی | بدون تغییر به next میروند. |
| upstream pause/resume | به next فرستاده میشود. |
| downstream payload | بدون تغییر به نود قبلی برمیگردد. |
downstream Est / pause / resume | به نود قبلی فرستاده میشود. |
callbackهای UpStreamEst و DownStreamInit برای این نود غیرفعال هستند.
Finish
با دریافت Finish در upstream، HeaderClient state محلی خود را از بین میبرد و finish را به next میفرستد.
با دریافت Finish در downstream، state محلی از بین میرود و finish به نود قبلی فرستاده میشود.
چون این تونل سازنده line نیست، lineDestroy() را فراخوانی نمیکند.
محدودیتها و Padding
| مقدار | اندازه |
|---|---|
| header دو بایتی WaterWall برای port | 2 bytes |
| header پروتکل PROXY v1 | حداکثر 108 bytes |
| header پروتکل PROXY v2 | 16 یا 28 bytes |
required_padding_left | 108 bytes |
از آنجا که این نود بزرگترین header پشتیبانیشده را با sbufShiftLeft() prepend میکند، مقدار required_padding_left = 108 را اعلام میکند.
نکتهها و محدودیتها
- فقط در chainهای stream لایه ۴ استفاده شود.
- نباید بهعنوان state مشترک packet-line استفاده شود.
- header دوبایتی port فقط metadata داخلی WaterWall است، نه یک پروتکل عمومی.
- حالتهای PROXY protocol فعلاً فقط IPv4 را پشتیبانی میکنند.
- destination IP در PROXY protocol از
settings.frontend-ipv4میآید، نهdest_ctx. - اگر یک listener روی چند frontend IPv4 ترافیک قبول کند، یک مقدار ثابت
frontend-ipv4ممکن است همه اتصالهای پذیرفتهشده را درست توصیف نکند. - مقدار encodeشده هنگام رسیدن نخستین payloadِ upstream خوانده میشود.
- تنها نخستین payloadِ upstream تغییر میکند و مسیر پاسخ بدون تغییر است.