Модуль Real IP nginx используется для решения проблем с IP-адресами, которые могут возникнуть, когда прокси-сервер перенаправляет запросы на nginx. Потому что, когда PROXY получает запрос клиента, он подключается к серверу nginx через свой собственный IP-адрес и пересылает запрос. В результате IP-адрес, зарегистрированный в приложении nginx, будет адресом прокси-сервера, а не адресом фактического клиента.
Пример показан ниже:
Когда nginx получает HTTP-запрос от клиента, поскольку он прошел через промежуточный прокси-сервер PROXY, NGINX по умолчанию может знать только IP-адрес интрасети 192.168.0.1 от PROXY, а не реальный IP-адрес клиента 111.22.33.44. Поэтому необходим механизм, позволяющий NGINX получать реальный IP-адрес клиента. К счастью, протокол HTTP может прозрачно передавать реальный IP-адрес клиента на серверную часть через заголовок X-Forwarded-For или заголовок X-Real-IP. Например, когда ПРОКСИ получает запрос, он Заголовок X-Forwarded-For или заголовок X-Real-IP с IP-адресом клиента будет добавлен к заголовку запроса клиента, а затем перенаправлен на внутренний сервер NGINX. Сервер NGINX должен получить его из соответствующего заголовка HTTP-запроса. по договору Реальный IP клиента.
Задача модуля Real IP — восстановить реальный IP-адрес клиента, переданный прокси-сервером, до фактического IP-адреса клиента, чтобы модуль приложения в nginx мог получить реальный IP-адрес клиента.
В этой статье сначала описывается использование и настройка модуля Real IP, а затем анализируется исходный код модуля Real IP, чтобы получить более глубокое понимание механизма его реализации.
ngx_http_realip_module по умолчанию не включен. Поэтому вам необходимо включить этот модуль во время настройки следующим образом:
./configure --with-http_realip_module
1. команда настройки real_ip_header
язык Закон:
real_ip_header field | X-Real-IP | X-Forwarded-For | proxy_protocol;
значение по умолчанию:
real_ip_header X-Real-IP;
Контекст:
http, server, location
Эта команда используется для определения места получения реального IP-адреса клиента, чтобы NGINX мог получить его и заменить полученный реальный IP-адрес клиента на IP-адрес прокси-сервера.
Параметры включают заголовок X-Real-IP, заголовок X-Forwarded-For или пользовательский заголовок HTTP. Кроме того, он также поддерживает получение реального IP клиента через протокол proxy_protocol. Разумеется, чтобы использовать эту опцию, сначала нужно включить функцию proxy_protocol на nginx.
2. команда настройки real_ip_recursive
язык Закон:
real_ip_recursive on | off;
значение по умолчанию:
real_ip_recursive off;
Контекст:
http, server, location
Эта команда используется для включения или отключения использования многоуровневых прокси-серверов при получении реального IP-адреса клиента через заголовки, связанные с HTTP.
Если рекурсивный поиск отключен, исходный адрес клиента, соответствующий одному из доверенных адресов, будет заменен последним адресом в поле заголовка запроса, определенном директивой real_ip_header. Если включен рекурсивный поиск, исходный адрес клиента, соответствующий одному из доверенных адресов, будет заменен последним недоверенным адресом в поле заголовка запроса.
Включив опцию рекурсии, nginx может справиться с ситуацией, когда клиент и nginx использовали несколько прокси-серверов, nginx может удалить все IP-адреса ПРОКСИ через установленный список ПРОКСИ и найти первый IP-адрес, который не является ПРОКСИ, как реальный IP-адрес клиента. .
3. команда настройки set_real_ip_from
Определите адреса одного или нескольких доверенных ПРОКСИ-серверов в следующем формате:
язык Закон:
set_real_ip_from address | CIDR | unix:;
значение по умолчанию:
—
Контекст:
http, server, location
Команда set_real_ip_from может быть определена несколько раз. Определенный адрес ПРОКСИ-сервера может быть указан в форме IP/маски (т. е. CIDR) или в форме имени домена. Его также можно указать как «unix:» для выражения доверия. во всех unix-сокетах соединение установлено.
Например, так:
set_real_ip_from 192.168.0.0/24;
set_real_ip_from unix:
set_real_ip_from www.test_proxy.com;
Давайте сначала приведем пример:
http {
# ...
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# ...
}
Приведенный выше пример включает функцию Real IP, и nginx будет искать заголовок X-Forwarded-For, чтобы получить реальный IP-адрес клиента.
После того, как этот модуль получит реальный IP-адрес клиента, он заменит подключенный к нему IP-адрес на реальный IP-адрес клиента. Конечно, иногда нам все равно необходимо получить IP-адрес и порт ПРОКСИ, поэтому nginx также предоставляет его. соответствующий механизм для ее получения. То есть эта информация предоставляется через переменные. Включает две переменные следующим образом:
{ ngx_string("set_real_ip_from"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_realip_from,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
В функции обратного вызова ngx_http_realip_from, указанной в приведенной выше конфигурации, разрешенный адрес доверенного прокси-сервера будет сохранен в rlcf->fromвнутри массива。Например, следующий код:
if (ngx_strcmp(value[1].data, "unix:") == 0) {
cidr = ngx_array_push(rlcf->from);
if (cidr == NULL) {
return NGX_CONF_ERROR;
}
cidr->family = AF_UNIX;
return NGX_CONF_OK;
}
При анализе unix: в массив будет добавлен тип семейства. Запись для AF_UNIX. Для CDIR, представленного IP/MASK, будет проанализирован cdir (поддерживается адреса типа ipv4 и ipv6). длядоменное имя,Затем nginx вызовет ngx_inet_resolve_host для разрешения имени домена.,Следует отметить, что из,Эта операция разрешения доменного имени представляет собой синхронный запрос.,Если операция разрешения доменного имени выполняется медленно,может привести кnginxзапускатьизиногда застреваю。Разобрать этоизнесколькоIPАдреса будут добавляться один за другимrlcf->fromв массиве。
{ ngx_string("real_ip_header"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_realip,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
В функции обратного вызова ngx_http_realip, указанной в приведенной выше конфигурации, будет анализироваться тип источника реального IP-адреса клиента, включая стандартный HTTP-заголовок с именем X-Real-IP, X-Forwarded-For или собственный HTTP-заголовок. Вы также можете указать. proxy_protocol. Использовать реальный IP-адрес клиента, прозрачно передаваемый по протоколу proxy_protocol.
Исходный код выглядит следующим образом:
static char *
ngx_http_realip(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_realip_loc_conf_t *rlcf = conf;
ngx_str_t *value;
if (rlcf->type != NGX_CONF_UNSET_UINT) {
return "is duplicate";
}
value = cf->args->elts;
if (ngx_strcmp(value[1].data, "X-Real-IP") == 0) {
rlcf->type = NGX_HTTP_REALIP_XREALIP;
return NGX_CONF_OK;
}
if (ngx_strcmp(value[1].data, "X-Forwarded-For") == 0) {
rlcf->type = NGX_HTTP_REALIP_XFWD;
return NGX_CONF_OK;
}
if (ngx_strcmp(value[1].data, "proxy_protocol") == 0) {
rlcf->type = NGX_HTTP_REALIP_PROXY;
return NGX_CONF_OK;
}
/* Здесь предварительно рассчитайте поиск в заголовке HTTP-запроса по имени пользовательского HTTP-заголовка и хеш-значению.
для X-Real-IP и X-Forwarded-Forheadа, когда nginx анализирует заголовок запроса из
было автоматически установлено значениеr->headers_inпереписыватьсяизв поле,Таким образом, вы можете извлечь его напрямую, без поиска.,
Таким образом, только пользовательские заголовки HTTP должны вычислять хеш-значение.
*/
rlcf->type = NGX_HTTP_REALIP_HEADER;
rlcf->hash = ngx_hash_strlow(value[1].data, value[1].data, value[1].len);
rlcf->header = value[1];
return NGX_CONF_OK;
}
{ ngx_string("real_ip_recursive"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_realip_loc_conf_t, recursive),
NULL },
Директива конфигурации real_ip_recursive устанавливает только один флаг — рекурсивный.
Инициализация модуля завершается функцией ngx_http_realip_init, которая выполняется на этапе постконфигурации. Эта функция монтирует функцию обратного вызова на этапе NGX_HTTP_POST_READ_PHASE. Когда nginx завершает чтение заголовка запроса, он вызывает обратный вызов ngx_http_realip_handler. В то же время эта функция также монтирует функцию обратного вызова на этапе NGX_HTTP_PREACESS_PHASE, которая также является функцией ngx_http_realip_handler.
Почему нам нужно выполнять одну и ту же функцию обратного вызова в два этапа? Я думаю, что в нормальных обстоятельствах его нужно выполнить только на этапе NGX_HTTP_POST_READ_PHASE. Однако в случае, когда настроен пользовательский заголовок HTTP HEADER, этот модуль позволяет другим пользовательским модулям добавлять этот пользовательский HTTP перед этапом NGX_HTTP_PREACCESS_PHASE. Заголовок запроса HEADER анализируется ngx_http_realip_handler на этапе NGX_HTTP_PREACCESS_PHASE для получения реального IP-адреса клиента. Эта реализация является более гибкой. Способ получения реального IP-адреса клиента может быть настроен пользователем и даже может быть помещен в URL-адрес запроса. HTTP BODY середина.
Когда nginx получает входящий запрос, он вызывает функцию ngx_http_realip_handler на этапе NGX_HTTP_POST_READ_PHASE. Давайте проанализируем функцию ngx_http_realip_handler.
Сначала будет оценено, включен ли реальный ipФункция,Это переданоrlcf->fromЭтоNULLсудитьиз,Следующий код:
rlcf = ngx_http_get_module_loc_conf(r, ngx_http_realip_module);
if (rlcf->from == NULL) {
return NGX_DECLINED;
}
Если он равен NULL, возвращается NGX_DECLINED, остальная логика этого модуля пропускается, и nginx выполняет последующую обработку.
Следующим шагом является получение контекстной информации. Если контекстная информация уже существует, это означает, что текущий запрос был обработан этим модулем, и логика этого модуля напрямую пропускается, как показано в следующем коде:
ctx = ngx_http_realip_get_module_ctx(r);
if (ctx) {
return NGX_DECLINED;
}
Ниже необходимо обработать различные типы источников, установленные клиентом, для получения реального IP-адреса, а именно заголовка X-Real-IP, заголовка X-Forwarded-For, протокола proxy_protocol и других пользовательских заголовков. Исходный код такой. следует:
switch (rlcf->type) {
case NGX_HTTP_REALIP_XREALIP:
if (r->headers_in.x_real_ip == NULL) {
return NGX_DECLINED;
}
value = &r->headers_in.x_real_ip->value;
xfwd = NULL;
break;
case NGX_HTTP_REALIP_XFWD:
xfwd = r->headers_in.x_forwarded_for;
if (xfwd == NULL) {
return NGX_DECLINED;
}
value = NULL;
break;
case NGX_HTTP_REALIP_PROXY:
if (r->connection->proxy_protocol == NULL) {
return NGX_DECLINED;
}
value = &r->connection->proxy_protocol->src_addr;
xfwd = NULL;
break;
default: /* NGX_HTTP_REALIP_HEADER */
part = &r->headers_in.headers.part;
header = part->elts;
hash = rlcf->hash;
len = rlcf->header.len;
p = rlcf->header.data;
/* Просмотрите список заголовков http-запроса, чтобы найти пользовательские заголовки. */
for (i = 0; /* void */ ; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
header = part->elts;
i = 0;
}
if (hash == header[i].hash
&& len == header[i].key.len
&& ngx_strncmp(p, header[i].lowcase_key, len) == 0)
{
value = &header[i].value;
xfwd = NULL;
goto found;
}
}
/* Если пользовательскую голову невозможно найти, пропустите этот модуль и другую логику. */
return NGX_DECLINED;
}
Наконец, полученному реальному IP-адресу клиента присваивается значение sockaddr в объекте соединения c. Исходный код выглядит следующим образом:
found:
c = r->connection;
addr.sockaddr = c->sockaddr;
addr.socklen = c->socklen;
/* addr.name = c->addr_text; */
if (ngx_http_get_forwarded_addr(r, &addr, xfwd, value, rlcf->from,
rlcf->recursive)
!= NGX_DECLINED)
{
if (rlcf->type == NGX_HTTP_REALIP_PROXY) {
ngx_inet_set_port(addr.sockaddr, c->proxy_protocol->src_port);
}
/* Установите реальный IP-адрес клиента */
return ngx_http_realip_set_addr(r, &addr);
}
return NGX_DECLINED;
Функция ngx_http_realip_handler получает поля, которые можно использовать для получения реального IP-адреса клиента в соответствии с набором различных категорий, а затем вызывает ngx_http_get_forwarded_addr для получения IP-адреса.
Необходимо понимать, что этот модуль использует две стратегии обработки для X-Real-IP и пользовательских заголовков, а также X-Forwarded-For. В первом случае просто установите поле значения перед вызовом ngx_http_get_forwarded_addr, а во втором будет установлено поле xfwd для сохранения всех заголовков X-Forwarded-For (возможно, более одного, но нескольких).
Для запросов, отличных от X-Forwarded-For, в функции ngx_http_get_forwarded_addr выполните следующую логику:
if (headers == NULL) {
return ngx_http_get_forwarded_addr_internal(r, addr, value->data,
value->len, proxies,
recursive);
}
Для запросов X-Forwarded-For в функции ngx_http_get_forwarded_addr в этом случае при включенной рекурсии нужно пройти по всем одноименным заголовкам X-Forwarded-For и выполнить следующую логику:
/* Отсортируйте заголовок X-Forwarded-For в обратном порядке, в порядке, указанном в заголовке HTTP-запроса. */
for (h = headers, headers = NULL; h; h = next) {
next = h->next;
h->next = headers;
headers = h;
}
/* iterate over all headers in reverse order */
rc = NGX_DECLINED;
found = 0;
/* Пройти все X-Forwarded-Forheadа, извлечь IP-адрес клиента */
for (h = headers; h; h = h->next) {
rc = ngx_http_get_forwarded_addr_internal(r, addr, h->value.data,
h->value.len, proxies,
recursive);
if (!recursive) { /* Если режим рекурсии не включен, может обрабатываться только один заголовок X-Forwarded-For. */
break;
}
if (rc == NGX_DECLINED && found) {
rc = NGX_DONE; /* Когда рекурсияиз включена, возвращается первый ненадежный IP-адрес сзади вперед. */
break;
}
if (rc != NGX_OK) {
break;
}
/* Когда режим рекурсии включен, он продолжит поиск следующего заголовка X-Forwarded-For. */
found = 1;
}
/* Восстановите все заголовки X-Forwarded-For в исходном порядке. */
for (h = headers, headers = NULL; h; h = next) {
next = h->next;
h->next = headers;
headers = h;
}
return rc;
Ниже приведен исходный код реализации ngx_http_get_forwarded_addr_internal:
static ngx_int_t
ngx_http_get_forwarded_addr_internal(ngx_http_request_t *r, ngx_addr_t *addr,
u_char *xff, size_t xfflen, ngx_array_t *proxies, int recursive)
{
u_char *p;
ngx_addr_t paddr;
ngx_uint_t found;
found = 0;
do {
/* ngx_cidr_matchфункция возвращает NGX_OK при совпадении сегмента изIP в заданном списке прокси
В противном случае верните NGX_DECLINED. */
if (ngx_cidr_match(addr->sockaddr, proxies) != NGX_OK) {
/* Если совпадения ранее не было, верните NGX_DECLINED.
Если ранее было совпадение, возвращается NGX_DONE.
выражать Когда рекурсияиз включена, возвращается первый ненадежный IP-адрес сзади вперед. */
*/
return found ? NGX_DONE : NGX_DECLINED;
}
/* Обратный поиск следующего IP-адреса. Используйте пробелы или пробелы для разделения нескольких IP-адресов. */
for (p = xff + xfflen - 1; p > xff; p--, xfflen--) {
if (*p != ' ' && *p != ',') {
break;
}
}
for ( /* void */ ; p > xff; p--) {
if (*p == ' ' || *p == ',') {
p++;
break;
}
}
/* На основе приведенного выше текста определите реальный IP-адрес клиента. */
if (ngx_parse_addr_port(r->pool, &paddr, p, xfflen - (p - xff))
!= NGX_OK)
{
return found ? NGX_DONE : NGX_DECLINED;
}
*addr = paddr;
found = 1;
xfflen = p - 1 - xff;
} while (recursive && p > xff); /* Продолжить сканирование с включенной рекурсияиз */
return NGX_OK;
}
Подведем итог: логика приведенного выше кода заключается в том, что при отключении рекурсии, когда IP-адрес, подключенный к nginx, находится в списке PROXY, будет возвращен HTTP. Последний IP-адрес, установленный в заголовке HEADER, в противном случае действие по настройке реального IP-адреса клиента не будет выполнено, а при включенной рекурсии, когда IP-адрес, подключенный к nginx, находится в списке PROXY, заголовок HTTP-запроса будет установлен из Among; IP-адреса расположены задом наперед, первый IP-адрес, которого нет в списке ПРОКСИ, всегда считается реальным IP-адресом клиента. В противном случае действие по настройке реального IP-адреса клиента не будет выполнено.
Таким образом, включив рекурсивную опцию, nginx может справиться с ситуацией, когда клиент и nginx используют несколько прокси-серверов. nginx может выделить все IP-адреса ПРОКСИ через установленный список ПРОКСИ и найти первый IP-адрес, который не является ПРОКСИ. реальный IP-адрес клиента. Но несмотря ни на что, вам необходимо убедиться, что адрес ПРОКСИ, который имеет реальное TCP-соединение с nginx, должен быть в списке ПРОКСИ. Только тогда nginx сможет получить реальный IP-адрес клиента, а затем установить реальный IP-адрес клиента.
После получения реального IP-адреса клиента этот модуль вызывает функцию ngx_http_realip_set_addr для установки реального адреса клиента.
Во-первых, необходимо преобразовать IP-адрес в текст. Исходный код выглядит следующим образом:
len = ngx_sock_ntop(addr->sockaddr, addr->socklen, text,
NGX_SOCKADDR_STRLEN, 0);
if (len == 0) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
После вышеуказанной операции текстовая информация IP-адреса помещается в массив текстовых строк. Затем выделите пространство памяти из пула памяти для сохранения содержимого массива текстовых строк. Исходный код выглядит следующим образом:
p = ngx_pnalloc(c->pool, len);
if (p == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(p, text, len);
Установите контекстную информацию модуля, исходный код выглядит следующим образом:
ngx_http_set_ctx(r, ctx, ngx_http_realip_module);
ctx->connection = c;
ctx->sockaddr = c->sockaddr;
ctx->socklen = c->socklen;
ctx->addr_text = c->addr_text;
Как только контекст модуля установлен, это означает, что модуль обработал этот запрос и установил реальный IP клиента. Нет необходимости обрабатывать его повторно при повторном входе в функцию ngx_http_realip_handler.
Наконец, введите текстовую информацию об IP-адресе в поле информации о подключенном адресе. Исходный код выглядит следующим образом:
c->sockaddr = addr->sockaddr;
c->socklen = addr->socklen;
c->addr_text.len = len;
c->addr_text.data = p;
После выполнения вышеуказанной операции с исходным кодом вы получите вновь установленный реальный IP-адрес клиента, когда впоследствии получите IP-адрес клиента в других модулях nginx.
В приведенном выше процессе анализа механизм окончательной обработки после завершения запроса был намеренно пропущен. Давайте объясним ниже:
Завершающая обработка реализована с использованием механизма самого пула памяти nginx. Исходный код выглядит следующим образом:
cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_realip_ctx_t));
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
......
/* Установить функцию обратного вызова */
cln->handler = ngx_http_realip_cleanup;
......
Найдите объект перезапуска в пуле памяти текущего запроса. Функция обратного вызова, установленная в смонтированном объекте перезапуска, будет автоматически вызвана, когда пул памяти будет освобожден в конце запроса. Вот функция ngx_http_realip_cleanup.
ngx_http_realip_cleanup будет вызван обратно, когда текущий запрос завершится, и его основная задача — восстановить реальный IP-адрес клиента, установленный выше. Поскольку сеанс TCP-соединения клиента может инициировать несколько HTTP-запросов, nginx необходимо восстановить контекст соединения после завершения HTTP-запроса.
Так зачем нам восстанавливать контекст соединения? Разве нельзя использовать реальный IP-адрес подключенного в данный момент клиента до следующего запроса? Ответ — нет. Поскольку соединение между PROXY и nginx может быть повторно использовано PROXY, и когда поступит следующий запрос, вполне возможно, что это не исходный клиент. Если вы будете следовать этой логике, и PROXY повторно использует предыдущее соединение с nginx, это будет так. вполне возможно, что это «переход».
Исходный код следующий:
static void
ngx_http_realip_cleanup(void *data)
{
ngx_http_realip_ctx_t *ctx = data;
ngx_connection_t *c;
c = ctx->connection;
c->sockaddr = ctx->sockaddr;
c->socklen = ctx->socklen;
c->addr_text = ctx->addr_text;
}
Из исходного кода функции ngx_http_realip_cleanup мы видим, что никакой логики для переработки памяти нет, потому что логика переработки памяти полностью обрабатывается автоматически пулом памяти nginx, и нам не нужно об этом заботиться.