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

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

فیلدهای لازم​

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

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

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

فیلدنوعتوضیح
overridenumber یا 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 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 تنظیم‌شده buffer می‌شود.
upstream payload بعد از headerبه next فرستاده می‌شود.
upstream pause/resume قبل از headerنادیده گرفته می‌شود؛ سمت next هنوز مقداردهی اولیه نشده است.
upstream pause/resume بعد از headerبه next فرستاده می‌شود.
downstream Est / pause / resume قبل از headerنادیده گرفته می‌شود.
downstream payload قبل از headerbuffer‌ش برای استفاده مجدد برگردانده و سمت قبلی بسته می‌شود.
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 header2 bytes
WaterWall protocol header1 byte
حداقل 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" استفاده کنید.
  • حالت 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، HeaderServer node بعدی را مقداردهی اولیه نکرده است.
  • نباید به‌عنوان state مشترک packet-line استفاده شود.

متادیتای نود​

metadata برگرفته از کد منبع:

ویژگیمقدار
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left0 bytes

پشتیبانی از splice​

HeaderServer پرچم kNodeFlagSupportsSplice را دارد. هنگام انتظار برای header پورت، پروتکل TCP/UDP یا PROXY protocol، کل payload دریافتی را با sbufEnsureOrdinary() به حافظهٔ عادی تبدیل می‌کند و سپس به BufferStream می‌دهد. بایت‌های application همراه header حفظ می‌شوند. پس از تکمیل header، هر دو جهت و صف محدود reentry، buffer‌های splice را بدون تبدیل عبور می‌دهند. حالت پورت ثابت به خواندن payload نیاز ندارد.

برای parsing موقت، ترجیح خواندن عادی تنظیم نمی‌شود؛ این ترجیح تا پایان line باقی می‌ماند و خواندن splice بعدی را هم غیرفعال می‌کند.