PROXY Protocol — это сетевой протокол, предназначенный для передачи информации о реальном IP-адресе и порте клиента через прокси-сервер к бэкенд-серверу, сохраняя исходные метаданные соединения.
Что такое PROXY Protocol?
Когда клиент подключается к серверу через прокси, бэкенд-сервер видит IP-адрес прокси-сервера, а не реальный IP-адрес клиента. Это затрудняет ведение точных логов, применение IP-ориентированных правил безопасности, геолокацию и другие задачи, требующие информации об источнике соединения. PROXY Protocol решает эту проблему, инкапсулируя данные о реальном клиенте (IP-адрес, порт) и прокси-сервере (IP-адрес, порт) в специальный заголовок, который передается в начале TCP-соединения между прокси и бэкендом.
Протокол был разработан HAProxy и впоследствии стал стандартом де-факто для многих прокси-серверов и балансировщиков нагрузки, включая Nginx, Envoy, AWS ELB/NLB, Google Cloud Load Balancing и другие.
Как работает PROXY Protocol
PROXY Protocol работает путем добавления небольшого текстового (v1) или бинарного (v2) заголовка в начало каждого соединения, которое прокси-сервер устанавливает с бэкендом. Этот заголовок содержит следующую информацию:
* Версия протокола (v1 или v2).
* Тип протокола транспортного уровня (TCP4, TCP6, UDP4, UDP6).
* Реальный IP-адрес клиента (источник).
* Порт клиента (источник).
* IP-адрес прокси-сервера (назначение).
* Порт, который прокси использовал для соединения с бэкендом (назначение).
Бэкенд-сервер, поддерживающий PROXY Protocol, считывает этот заголовок, извлекает необходимую информацию и затем обрабатывает остаток соединения как обычный трафик. Если бэкенд-сервер не поддерживает PROXY Protocol, он будет интерпретировать заголовок как часть данных приложения, что приведет к ошибкам.
PROXY Protocol v1
PROXY Protocol v1 — это текстовая, человекочитаемая версия протокола. Она проще в отладке и понимании, но имеет ограничения по функциональности и производительности.
Формат PROXY Protocol v1
Заголовок v1 представляет собой одну строку текста, завершающуюся последовательностью \r\n.
Пример формата:
PROXY TCP4 192.168.0.1 192.168.0.100 1234 80\r\n
PROXY: Сигнатура, идентифицирующая протокол.TCP4: Указывает на использование IPv4 и TCP. Допустимы такжеTCP6для IPv6.192.168.0.1: IP-адрес клиента.192.168.0.100: IP-адрес прокси-сервера.1234: Исходный порт клиента.80: Порт назначения на прокси (тот, к которому подключился клиент).
Если прокси не может определить IP-адрес или порт клиента (например, из-за ошибки или если прокси не является прямым источником), он может отправить заголовок с информацией об UNKNOWN:
PROXY UNKNOWN\r\n
В этом случае бэкенд-сервер должен использовать IP-адрес и порт прокси-сервера как fallback.
Ограничения v1
- Только TCP: Поддерживает только TCP-соединения (IPv4 и IPv6).
- Текстовый формат: Накладывает небольшой оверхед на парсинг и увеличивает размер заголовка по сравнению с бинарным.
- Фиксированная структура: Не поддерживает передачу дополнительной информации.
PROXY Protocol v2
PROXY Protocol v2 — это бинарная версия протокола, разработанная для повышения эффективности, расширяемости и поддержки большего числа протоколов. Она сложнее для ручной отладки, но предпочтительнее для высоконагруженных систем.
Формат PROXY Protocol v2
Заголовок v2 начинается с 12-байтовой сигнатуры \x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x51\x0D\x0A\x7F, за которой следуют бинарные поля.
Основные поля заголовка v2:
- 12-байт сигнатура:
\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x51\x0D\x0A\x7F - 1-байт
ver_cmd:Версия(4 бита): Всегда0x2для v2.Команда(4 бита):0x0(LOCAL): Соединение было установлено на самом прокси-сервере. Не содержит адресной информации.0x1(PROXY): Соединение было перенаправлено прокси-сервером. Содержит адресную информацию.
- 1-байт
fam_prot:Семейство адресов(4 бита):0x0(UNSPEC): Неуказанное семейство (например, дляLOCALкоманды).0x1(AF_INET): IPv4.0x2(AF_INET6): IPv6.0x3(AF_UNIX): Unix-сокеты.
Протокол(4 бита):0x0(UNSPEC): Неуказанный протокол.0x1(STREAM): TCP или UNIX_STREAM.0x2(DGRAM): UDP или UNIX_DGRAM.
- 2-байта
len: Общая длина (в байтах) всех последующих полей (адреса и TLV).
После этих 16 байт следуют:
* Адресная информация: Зависит от fam_prot.
* Для AF_INET (IPv4): 4 байта Source IP, 4 байта Destination IP, 2 байта Source Port, 2 байта Destination Port. (12 байт всего)
* Для AF_INET6 (IPv6): 16 байт Source IP, 16 байт Destination IP, 2 байта Source Port, 2 байта Destination Port. (36 байт всего)
* Для AF_UNIX: 108 байт Source UNIX socket path, 108 байт Destination UNIX socket path. (216 байт всего)
* TLV (Type-Length-Value): Опциональные поля, позволяющие передавать дополнительную информацию. Каждое TLV состоит из:
* Type (1 байт): Тип данных (например, PP2_TYPE_SSL, PP2_TYPE_ALPN, PP2_TYPE_AUTHORITY).
* Length (2 байта): Длина значения.
* Value (Length байт): Сами данные.
Примеры TLV:
* PP2_TYPE_SSL: Информация о SSL/TLS соединении (версия, шифр, клиентский сертификат).
* PP2_TYPE_ALPN: Протокол прикладного уровня (например, h2, http/1.1).
* PP2_TYPE_UNIQUE_ID: Уникальный идентификатор соединения.
Преимущества v2
- Эффективность: Бинарный формат уменьшает размер заголовка и ускоряет его парсинг.
- Расширяемость: Поддержка TLV позволяет добавлять новые типы данных без изменения основного формата.
- Поддержка UDP и UNIX-сокетов: В отличие от v1.
- Гибкость: Команда
LOCALпозволяет прокси сообщать, что соединение не было перенаправлено.
Сравнение PROXY Protocol v1 и v2
| Характеристика | PROXY Protocol v1 | PROXY Protocol v2 |
|---|---|---|
| Формат | Текстовый, человекочитаемый | Бинарный |
| Поддерживаемые L3/L4 | TCP4, TCP6 | TCP4, TCP6, UDP4, UDP6, UNIX-сокеты |
| Размер заголовка | Переменный, но обычно 20-30 байт | Фиксированные 16 байт + размер адресной информации + TLV |
| Производительность | Немного ниже из-за текстового парсинга | Выше, благодаря бинарному формату |
| Расширяемость | Отсутствует | Присутствует через TLV (Type-Length-Value) |
| Отладка | Проще (можно прочитать Wireshark/tcpdump) | Сложнее (требует декодирования) |
| Случаи UNKNOWN | PROXY UNKNOWN\r\n |
LOCAL команда (ver_cmd=0x20) |
| Доп. информация | Нет | Да, через TLV (SSL, ALPN, Unique ID и т.д.) |
Реализация и конфигурация
Для использования PROXY Protocol требуется настройка как на прокси-сервере, так и на бэкенд-сервере.
Настройка прокси-сервера
Прокси-сервер должен быть сконфигурирован для добавления заголовка PROXY Protocol к исходящим соединениям.
HAProxy
В HAProxy это делается с помощью директив send-proxy (для v1) или send-proxy-v2 (для v2) в секции server или default_server.
listen http_proxy
bind *:80
mode tcp
default_backend webservers
backend webservers
mode tcp
server web1 192.168.1.10:80 send-proxy-v2 # Отправка PROXY Protocol v2
server web2 192.168.1.11:80 send-proxy # Отправка PROXY Protocol v1
Nginx (stream module)
Для проксирования TCP-трафика и добавления PROXY Protocol, Nginx использует stream модуль.
stream {
upstream backend_servers {
server 192.168.1.10:80;
server 192.168.1.11:80;
}
server {
listen 80;
proxy_pass backend_servers;
proxy_protocol on; # Включает отправку PROXY Protocol v1
# Для v2 требуются дополнительные директивы или сборка с модулем pp_v2
# или использование `proxy_protocol v2;` в Nginx Plus / более новых версиях
}
}
Примечание: в более новых версиях Nginx и Nginx Plus есть поддержка proxy_protocol v2;.
Envoy Proxy
Envoy по умолчанию поддерживает PROXY Protocol и может быть настроен для его использования в listener или cluster конфигурации.
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.filters.network.proxy_protocol
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.proxy_protocol.v3.ProxyProtocol
# Если Envoy _получает_ PROXY Protocol от предыдущего прокси
- name: envoy.filters.network.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
stat_prefix: ingress_tcp
cluster: service_cluster
# Для _отправки_ PROXY Protocol к бэкенду
proxy_protocol_config:
version: V2 # Или V1
clusters:
- name: service_cluster
connect_timeout: 0.25s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: service_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.1.10
port_value: 80
Настройка бэкенд-сервера
Бэкенд-сервер должен быть сконфигурирован для чтения заголовка PROXY Protocol и извлечения из него реального IP-адреса клиента.
Nginx (http module)
Для веб-сервера Nginx, работающего с HTTP-трафиком, нужно использовать директивы set_real_ip_from и real_ip_header в секции http, server или location.
http {
# ...
set_real_ip_from 192.168.1.0/24; # IP-адреса прокси-серверов
set_real_ip_from 10.0.0.0/8;
real_ip_header proxy_protocol; # Указывает Nginx использовать PROXY Protocol
# real_ip_recursive on; # Если есть несколько прокси
# ...
server {
listen 80 proxy_protocol; # Включает PROXY Protocol для этого сервера
# ...
}
}
Директива listen 80 proxy_protocol; указывает Nginx ожидать PROXY Protocol заголовок на порту 80. Если заголовок отсутствует, соединение будет отклонено.
Apache HTTP Server
Apache может обрабатывать PROXY Protocol с помощью модуля mod_remoteip.
<IfModule mod_remoteip.c>
RemoteIPProxyProtocol On
RemoteIPHeader X-Forwarded-For # Или другой заголовок, если требуется
RemoteIPInternalProxy 192.168.1.0/24 # IP-адреса ваших прокси
RemoteIPInternalProxy 10.0.0.0/8
</IfModule>
RemoteIPProxyProtocol On активирует поддержку PROXY Protocol.
Другие приложения и фреймворки
Если ваше приложение не использует веб-сервер, который поддерживает PROXY Protocol напрямую (например, если это кастомный TCP-сервер), вам потребуется реализовать парсинг заголовка PROXY Protocol в коде вашего приложения. Библиотеки для парсинга PROXY Protocol доступны для большинства языков программирования.
Пример псевдокода для парсинга v1:
function parse_proxy_protocol_v1(socket_stream):
first_line = read_line_from_stream(socket_stream)
if starts_with(first_line, "PROXY"):
parts = split(first_line, " ")
if len(parts) == 6:
protocol = parts[1]
client_ip = parts[2]
proxy_ip = parts[3]
client_port = parts[4]
proxy_port = parts[5]
# Используйте client_ip и client_port
else if parts[1] == "UNKNOWN":
# Используйте IP и порт сокета, так как клиентский IP неизвестен
else:
# Ошибка парсинга, закрыть соединение или использовать IP сокета
else:
# Это не PROXY Protocol, обработать как обычное соединение
Сценарии использования и преимущества
- Точное логирование: Веб-серверы и приложения могут записывать в логи реальные IP-адреса клиентов, что критично для аналитики и аудита.
- Безопасность: Применение IP-ориентированных правил безопасности (брандмауэры, WAF, списки доступа) становится возможным на уровне бэкенд-сервера.
- Геолокация: Точное определение географического положения пользователя для персонализации контента или региональных ограничений.
- Ограничение частоты запросов (Rate Limiting): Применение лимитов на основе реального IP-адреса клиента.
- Балансировка нагрузки: Сохранение информации о клиенте может использоваться для более интеллектуальных алгоритмов балансировки нагрузки, например, для "липких сессий" (session stickiness).
- SSL/TLS Offloading: Если SSL-терминирование происходит на прокси, PROXY Protocol v2 может передавать информацию о параметрах SSL-соединения (версия, шифр, сертификат клиента) бэкенду.
Рекомендации и лучшие практики
- Доверие к источникам: PROXY Protocol должен приниматься только от доверенных прокси-серверов. Несанкционированный источник может подделать заголовок PROXY Protocol, что приведет к ложной информации о клиенте. Настройте бэкенд-серверы так, чтобы они принимали PROXY Protocol только от IP-адресов ваших прокси.
- Совместимость: Убедитесь, что и прокси, и бэкенд-сервер поддерживают выбранную версию PROXY Protocol. Несоответствие версий или отсутствие поддержки приведет к ошибкам соединения.
- Фаерволы и ACL: Убедитесь, что правила фаервола между прокси и бэкендом разрешают трафик.
- Тестирование: Всегда тщательно тестируйте конфигурацию PROXY Protocol в тестовой среде перед внедрением в продакшн.
- Мониторинг: Отслеживайте логи бэкенд-серверов на предмет ошибок, связанных с PROXY Protocol, чтобы оперативно выявлять проблемы.
- Выбор версии: В новых развертываниях предпочтительнее использовать PROXY Protocol v2 из-за его эффективности, расширяемости и поддержки UDP. v1 может быть оправдан для простых случаев или обратной совместимости.