Вам нужны отдельные директивы listen для IPv4 и IPv6 в nginx?

Вопрос или проблема

Я видел разные примеры конфигурации для обработки виртуальных хостов с двойным стеком IPv4 и IPv6 на nginx. Многие предлагают следующий шаблон:

listen 80;
listen [::]:80 ipv6only=on;

Насколько я могу видеть, это достигает точно такого же результата, как:

listen [::]:80 ipv6only=off;

Почему вы бы использовали первое? Единственная причина, которую я могу представить, это если вам нужны дополнительные параметры, специфичные для каждого протокола, например, если вы хотели установить deferred только для IPv4.

Это, вероятно, и является единственной причиной, по которой вы бы использовали первую конструкцию в наши дни.

Причина, по которой вы это видите, вероятно, в том, что значение по умолчанию ipv6only изменилось в nginx 1.3.4. До этого оно по умолчанию было установлено на off; в более новых версиях оно по умолчанию установлено на on.

Это взаимодействует с параметром сокета IPV6_V6ONLY в Linux и аналогичными параметрами в других операционных системах, значения по умолчанию для которых не всегда предсказуемы. Таким образом, первая конструкция была необходима до версии 1.3.4, чтобы гарантировать, что вы действительно слушаете подключения как по IPv4, так и по IPv6.

Изменение значения по умолчанию для ipv6only в nginx гарантирует, что значение по умолчанию операционной системы для сокетов с двойным стеком становится несущественным. Теперь nginx либо явно связывается с IPv4, IPv6, или обоими, никогда не полагаясь на ОС для создания сокета с двойным стеком по умолчанию.

Действительно, моя стандартная конфигурация nginx для версий до 1.3.4 имеет первую конфигурацию, а после 1.3.4 все имеют вторую конфигурацию.

Хотя, поскольку связывание сокета с двойным стеком является исключительно элементом Linux, моя текущая конфигурация теперь выглядит скорее как первый пример, но без установленного ipv6only, а именно:

listen [::]:80;
listen 80;

Если вы хостите несколько доменов vhost на одном экземпляре Nginx, вы не можете использовать единую комбинированную директиву прослушивания

listen [::]:80 ipv6only=off;

для каждого из них. У Nginx есть странная особенность, что вы можете указать параметр ipv6only только один раз для каждого порта, иначе он не сможет запуститься. Это означает, что вы не можете указать его для каждого блока серверов домена vhost.

Как упомянул Майкл, начиная с Nginx 1.3.4, параметр ipv6only по умолчанию установлен на on.

Таким образом, если вы хотите хостить несколько доменов как на IPv4, так и на IPv6 с помощью одного сервера Nginx, вам придется использовать две директивы прослушивания для каждого блока серверов домена:

listen 80;
listen [::]:80; 

Кроме того, как упомянул Сандер, использование ipv6only=off имеет тот недостаток, что IPv4 адреса переводятся в IPv6. Это может вызвать проблемы, если ваше приложение выполняет проверку IP на черных списках, таких как Akismet или StopForumSpam, потому что, если вы не создадите слой обратного перевода, ваше приложение будет проверять IPv6 перевод адреса спамера IPv4, который не совпадает ни с одним из адресов IPv4 в черном списке.

С конфигурацией ipv6only=off IPv4 адреса могут отображаться как IPv6 адреса с использованием (только программного) IPv4-отображенных IPv6 адресов, например, в файлах журналов, переменных окружения (REMOTE_ADDR) и т.д.

На мой взгляд (и согласно документации на http://nginx.org/en/docs/http/ngx_http_core_module.html#listen), использование только listen 80; достаточно, если вы хотите направить как IPv4, так и IPv6 трафик на один и тот же порт.

Переработанный ответ на ноябрь 2021 года

На ноябрь 2021 года с последней версией Nginx (из официального репозитория), например, на Ubuntu 18.04 или 20.04, я могу подтвердить, что для обычных (=не по умолчанию) vhosts Nginx это работает для трафика IPv4 и IPv6:

listen [::]:80;

…и если вы используете отдельный блок для HTTPS трафика:

listen [::]:443 ssl http2;

Флаг ipv6only=off должен быть упомянут ТОЛЬКО один раз и в “дефолтном” vhost в Nginx (который используется Nginx, когда ни один домен не может быть сопоставлен с vhost).

Например:

server {
    listen [::]:80 default_server ipv6only=off;

    # остальная часть вашей конфигурации vhost Nginx здесь...
}

server {
    listen [::]:443 default_server ssl http2 ipv6only=off;

    # остальная часть вашей конфигурации vhost Nginx здесь...
}

Очевидно, если ваша настройка Nginx использует один vhost, то вам нужна только последняя конфигурация.

Одна неприятная проблема, с которой я столкнулся, добавляя поддержку IPv6 на сайт с фрагментом listen [::]:80 ipv6only=off;, возникла, когда я добавил его в vhost, и default_server уже был настроен для прослушивания как 80, так и [::]:80.

nginx отказался запуститься, жалуясь, что адрес уже используется!

Замена волшебного listen [::]:80 ipv6only=off; на две традиционные строки listen позволяет nginx запуститься без проблем.

Несмотря на то, что listen [::]:80 ipv6only=off; может быть удобным в ручной настройке, это может вызвать неприятные проблемы при использовании в автоматизированных системах конфигурации.

Для тех, для кого ни один из других ответов не объяснил всю картину, я хочу предложить свой собственный.

В Linux есть флаг/опция IPV6_V6ONLY (см., например, man ipv6), который может быть установлен на сокетах. Опция устанавливается/очищается с помощью системной функции setsockopt. По умолчанию состояние флага соответствует содержимому файла /proc/sys/net/ipv6/bindv6only (0 для очищенного, т.е. не только-IPv6, 1 для установленного, т.е. только-IPv6).

Nginx использует указанную опцию, которую он явно устанавливает (переопределяя системное значение по умолчанию), так что listen [::]:<port> (где <port> – это номер порта) эквивалентно listen [::]:<port> ipv6only=on, что создаст [IPv6] сокет, который не принимает подключения IPv4.

Когда вы хотите поддерживать обе версии IP-протокола, вышеупомянутое положение можно смягчить, используя как минимум два решения.

Первое – это просто заставить Nginx слушать на двух отдельных сокетах явно, добавив директиву listen *:<port> (* – это подстановочный знак для адреса IPv4, никогда не IPv6) к уже упомянутой директиве listen, например, используя порт 80:

listen *:80;
listen [::]:80;

Второе решение создаст единственный сокет, для которого опция IPV6_V6ONLY будет очищена Nginx. Этот сокет примет как IPv4, так и IPv6 подключения. Одной директивы listen достаточно для этого, например, снова с портом 80:

listen [::]:80 ipv6only=off;

Программу lsof можно использовать для просмотра, сколько сокетов есть у процесса, предположим, у некоторого процесса с PID 555:

lsof -a -p 555 -i -T f

(-p 555 используется для перечисления сокетов процесса с PID 555, -i для перечисления интернет-сокетов (IPv4 и/или IPv6), -P для печати номеров портов вместо их названий, и -T f для перечисления дополнительной информации о каждой записи, такой как состояние прослушивания сокета; -a используется для формирования “и” запроса — все условия должны быть истинными, чтобы lsof перечислила запись; для этой цели переключатель работает примерно так же, как -and для find)

Ответ или решение

Вопрос, стоит ли использовать отдельные директивы listen для IPv4 и IPv6 в конфигурации Nginx, является актуальным для многих администраторов серверов, особенно тех, кто работает с многослойной сетевой архитектурой. Рассмотрим ключевые аспекты данной темы, основываясь на предоставленной информации.

1. Понимание директив listen

В Nginx директивы listen отвечают за настройки прослушивания определённых портов и IP-адресов. Применение разных директив для IPv4 и IPv6 может зависеть от конкретных требований вашего приложения и настроек системы.

Пример 1:

listen 80;
listen [::]:80 ipv6only=on;

В данном примере создаются две директивы: первая — для IPv4, вторая — для IPv6, с установленным флагом ipv6only=on. Эта настройка гарантирует, что IPv6 сокет будет слушать только IPv6-подключения.

Пример 2:

listen [::]:80 ipv6only=off;

При использовании этой директивы сокет будет принимать как IPv4, так и IPv6 соединения. Однако при этом IPv4 адреса могут отображаться как IPv4-замеченные IPv6 адреса в логах и приложениях, что может вызвать проблемы с системами проверки IP.

2. Изменение поведения Nginx в зависимости от версии

С версии Nginx 1.3.4 поведение директивы ipv6only изменилось. По умолчанию она устанавливается в значение on, что влияет на то, как Nginx управляет сокетами. Это позволяет избежать зависимости от настроек операционной системы, которые могут различаться.

3. Практическое применение в конфигурациях

Если вы планируете использовать несколько виртуальных хостов, вы столкнётесь с ограничением: параметр ipv6only можно указать только один раз на определённый порт. Это означает, что для каждого виртуального хоста, вам потребуется задавать две отдельные директивы listen.

Пример для нескольких виртуальных хостов:

server {
    listen 80;
    listen [::]:80;

    # остальная конфигурация виртуального хоста
}

4. Потенциальные проблемы с совместимостью

Использование ipv6only=off связано с рядом потенциальных проблем:

  • Отображение IP-адресов: IPv4 адреса могут отображаться в виде IPv4-замеченных адресов в логах, что затруднит последующий анализ и может нарушить работу приложений, использующих черные списки и подобные механизмы.
  • Ошибки при запуске Nginx: Если конфигурация виртуального хоста объявляет всё тот же порт с разными директивами, это может привести к ошибкам при запуске, как упомянуто в исходном тексте.

Заключение

С учетом описанных аспектов, рекомендуется использовать отдельные директивы listen для IPv4 и IPv6, особенно в сценариях с несколькими виртуальными хостами. Это обеспечивает большую гибкость, контролируя поведение каждого типового IP и избегая потенциальных проблем с совместимостью. При формировании конфигураций учитывайте также требования вашего приложения и специфические настройки системы, чтобы избежать неожиданных проблем при развертывании.

Оцените материал
Добавить комментарий

Капча загружается...