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

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 باقی بماند.

فیلدهای لازم

فیلدهای سطح اصلی:

فیلدنوعتوضیح
namestringنام دلخواه و یکتای نود.
typestringباید دقیقاً "HeaderServer" باشد.
settingsobjectاجباری است و باید override داشته باشد.
nextstringنود stream بعدی در لایه ۴ که line مسیریابی‌شده را می‌گیرد.

فیلد لازم داخل settings:

فیلدنوعتوضیح
overridenumber یا 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 v1PROXY TCP4 <source-ip> <dest-ip> <source-port> <dest-port>\r\n
PROXY v2version 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 Initstate محلی مقداردهی اولیه می‌شود، اما ارسال 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 header2 bytes
حداقل port قابل قبول از WaterWall header10
PROXY protocol v1 headerحداکثر 108 bytes
PROXY protocol v2 base header16 bytes
address block لازم برای PROXY protocol v2 TCP412 bytes
required_padding_left0 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 استفاده شود.