Как проверить и включить AES-NI на VPS
Узнайте, поддерживает ли ваш VPS инструкции AES-NI. Мы покажем, как замерить падение производительности при скрытом CPUID и принудительно активировать ускорение через OPENSSL_ia32cap.
Что на самом деле дает AES-NI на VPS
AES-NI на VPS — это набор из шести инструкций x86, которые выполняют один раунд AES (advanced encryption standard) на аппаратном уровне. Если модель процессора вашего провайдера скрывает их, физически они все равно присутствуют в кристалле, но OpenSSL не видит их и переключается на программную реализацию, которая требует примерно в десять раз больше циклов на байт. Вы можете проверить наличие этой функции одной командой, измерить разницу в производительности двумя и часто принудительно включить быстрый путь с помощью одной переменной окружения.
Эти инструкции — AESENC, AESENCLAST, AESDEC, AESDECLAST, AESIMC и AESKEYGENASSIST. Intel внедрила их в 2010 году, AMD последовала за ней, поэтому любой серверный процессор, который вы арендуете, имеет эти аппаратные возможности. Сопутствующая инструкция PCLMULQDQ выполняет умножение без переноса (carry-less multiplication), которое необходимо режиму GCM (Galois/counter mode) для создания аутентификационного тега. AES-GCM работает быстро только при наличии обеих инструкций, так как шифрование и создание тега — это отдельные задачи.
Четыре области на VPS, где это отражается в мониторинге:
- TLS (transport layer security) termination. Веб-сервер, использующий AES-128-GCM или AES-256-GCM, тратит большую часть времени на криптографические операции внутри AES.
- Шифрованные тома. LUKS (Linux unified key setup) и dm-crypt выполняют
aes-xtsпри каждой операции чтения и записи в ядре на уровне процессора. - VPN-трафик на базе AES. OpenVPN с
AES-256-GCMи IPsec с AES-GCM активно используют эти инструкции. - Шифрованные резервные копии. Любой процесс, шифрующий поток данных с помощью AES перед отправкой с сервера, несет те же затраты ресурсов.
Одна распространенная рабочая нагрузка не зависит от этого вовсе. WireGuard использует ChaCha20-Poly1305 для своих данных и никогда не обращается к AES, поэтому самостоятельно развернутый WireGuard VPN работает с той же скоростью даже на хосте, где этот флаг скрыт. Эта разница — весомый практический аргумент, чтобы сравнить WireGuard и OpenVPN перед выбором туннеля для недорогого VPS.
Как проверить наличие AES-NI на вашем VPS
Ядро копирует биты функций CPUID в /proc/cpuinfo, поэтому один вызов grep дает ответ на этот вопрос.
grep -m1 -o '\baes\b' /proc/cpuinfo
lscpu | grep -i -o '\baes\b'Если любая из команд выводит aes, это означает, что процессор сообщает гостевой системе о поддержке AES-NI. Отсутствие вывода означает, что поддержка отсутствует. lscpu считывает те же флаги, поэтому результаты всегда совпадают. Используйте ту утилиту, которая установлена в системе.
Теперь посмотрите, какой процессор, по утверждению хоста, вы используете.
grep -m1 'model name' /proc/cpuinfoНазвание реальной модели, например Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz или AMD EPYC 7443P 24-Core Processor, означает, что хост передает вам данные о физической модели процессора. QEMU Virtual CPU version 2.5+ или Common KVM processor означают, что происходит что-то иное, и этот случай стоит изучить подробнее.
Почему флаг отсутствует, хотя процессор его поддерживает
CPUID — это инструкция, которую программа использует для запроса возможностей процессора. Внутри виртуальной машины CPUID всегда перехватывается гипервизором, поэтому именно гипервизор решает, что именно «увидит» гостевая система. Большинство панелей управления предоставляют этот выбор в виде настройки модели гостевого процессора. qemu64 и kvm64 — это базовые универсальные модели, и ни одна из них не включает AES-NI или SSSE3 в свой набор функций, поэтому гостевая система не видит флаг aes, даже если физический хост — современный EPYC. VPS является гостем на чужом оборудовании, поэтому каждая функция, о которой он сообщает, — это решение, принятое на уровень выше. Если эта многоуровневая архитектура для вас в новинку, начните с что такое VPS.
Хосты намеренно выбирают универсальную модель, так как «живая» миграция между машинами с разными процессорами работает только в том случае, если гостевой системе не сообщалось о функциях, отсутствующих на целевом узле. Издержки ложатся на вас. Ваше ядро и ваша копия OpenSSL считывают этот маскированный CPUID один раз при запуске, после чего выбирают медленный путь выполнения кода на всё время работы процесса.
Решение на стороне источника — это настройка на стороне хоста: -cpu host в терминах QEMU, именованная модель, включающая AES-NI, или явный +aes, добавленный к модели. Вы не можете настроить это изнутри гостевой системы. Открытие тикета в службу поддержки или выбор тарифного плана, гипервизор которого пробрасывает модель процессора напрямую (passthrough), являются надежным способом решения проблемы.
Измерение разрыва производительности с помощью openssl speed
Не принимайте опубликованные результаты тестов на веру. Запустите тест того шифра, который фактически использует ваш сервер.
openssl version
openssl speed -evp aes-128-gcmСтрока результата помечена как AES-128-GCM и показывает пропускную способность для шести размеров блоков в единицах 1000 байт в секунду. Для оценки массовой передачи данных смотрите столбец 8192 байта, так как столбец 16 байт отражает в основном накладные расходы на вызов и не дает представления о скорости загрузки файлов.
Теперь выполните ту же команду с отключенными программно AES-NI и PCLMULQDQ:
OPENSSL_ia32cap="~0x200000200000000" openssl speed -evp aes-128-gcmЭто значение взято из документации OpenSSL по вектору возможностей. Префикс ~ означает «очистить эти биты». Бит 57 соответствует AES-NI, а бит 33 — PCLMULQDQ, поэтому 0x200000200000000 указывает именно на эти два бита и ничего больше. Если второе число значительно меньше первого, значит, на вашем сервере работает AES-NI, и вы закончили. Если два числа совпадают, значит, OpenSSL уже использовал программный путь, так как флаг не был установлен и очищать было нечего.
Данные, на которых построен график
[
{
"label": "AES-NI and PCLMULQDQ",
"mb_per_sec": "4,850",
"cycles_per_byte": 0.7
},
{
"label": "Software fallback",
"mb_per_sec": 310,
"cycles_per_byte": 11.0
}
]Это репрезентативные опубликованные показатели для современного ядра x86 с частотой около 3.4 ГГц, а не измерения, полученные на конкретном хосте. Воспринимайте их как ориентир. Аппаратный путь работает примерно с затратами 0.7 циклов на байт, а программный — примерно 11.0, что составляет около 4,850 МБ/с против 310 МБ/с на одно ядро. Две команды выше дают единственные числа, описывающие именно ваш сервер. Тот же подход применим и к остальным компонентам системы, поэтому используйте это вместе с воспроизводимым способом тестирования VPS, прежде чем делать выводы о выборе тарифного плана.
Принудительное включение битов через OPENSSL_ia32cap
Этот момент часто вызывает удивление. Инструкции AES-NI являются непривилегированными, и гипервизор не перехватывает их выполнение. Перехватывается только CPUID. Таким образом, хост может сообщить гостевой системе об отсутствии AES-NI, в то время как AESENC продолжает выполняться на полной скорости. Программное обеспечение пропускает быстрый путь выполнения, так как получило неверный ответ от CPUID. Сама инструкция при этом никогда не переставала работать.
OpenSSL позволяет ответить за процессор самостоятельно. Шестнадцатеричное значение в OPENSSL_ia32cap перезаписывает вектор возможностей, а не маскирует его.
OPENSSL_ia32cap="0x0200020207000000" openssl speed -evp aes-128-gcmЕсли этот запуск проходит в несколько раз быстрее обычного, значит, кремний поддерживает AES-NI, а хост скрывает это. Это прежде всего способ диагностики. Для OpenSSL это также является исправлением.
Как формируется это шестнадцатеричное значение
Первый логический вектор упаковывает EDX из листа CPUID 1 в младшие 32 бита, а ECX из того же листа — в старшие 32 бита. В младшей половине бит 24 отвечает за FXSR, бит 25 — за SSE, а бит 26 — за SSE2, что дает 0x07000000. В верхней половине бит 33 — это PCLMULQDQ, бит 41 — SSSE3, а бит 57 — AES-NI, что дает 0x02000202. В сумме это составляет 0x0200020207000000. SSSE3 включен в список, так как GHASH на базе PCLMULQDQ в OpenSSL использует pshufb для перестановки байтов, а стандартная модель гостевого процессора скрывает SSSE3 вместе с AES-NI.
Существует два предупреждения, и вы можете намеренно спровоцировать оба.
Установка только первого вектора оставляет последующие векторы нулевыми, что отключает пути выполнения AVX2 и AVX-512. В данном случае это сделано намеренно. Не пытайтесь принудительно включить биты AVX на гостевой системе с маскировкой, так как для регистров AVX требуется, чтобы операционная система включила расширенное состояние в XCR0, а ваше ядро отказалось это делать на основе того же замаскированного CPUID. В результате VEX-кодированная инструкция вызовет ошибку «неопределенный код операции» (undefined-opcode fault), и процесс завершится.
Принудительная активация AES-NI на ядре, которое физически не поддерживает эту инструкцию, немедленно завершает процесс:
Illegal instruction (core dumped)Это AESENC вызывает ошибку «неопределенный код операции», так как на этом ядре нет такой инструкции для выполнения. Некоторые версии прошивок серверов также могут аппаратно отключать AES-NI до следующей перезагрузки, и симптом будет идентичным. В любом случае решением является смена хоста, а не переменной окружения.
Чтобы сохранить переопределение для долго работающего сервиса, используйте drop-in файл systemd.
sudo systemctl edit nginx[Service]
Environment="OPENSSL_ia32cap=0x0200020207000000"sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl show nginx -p EnvironmentПоследняя команда должна вывести значение переменной обратно. Понимайте, на что вы идете: если этот сервер будет перенесен на хост, процессор которого действительно не поддерживает AES-NI, nginx завершится с ошибкой «недопустимая инструкция» (illegal instruction) при первом же TLS-соединении. Запишите это в свой регламент или вовсе не используйте переопределение в production, применяя его только для подтверждения проблемы при открытии тикета.
Что не исправляет эта переменная
OPENSSL_ia32cap влияет только на OpenSSL и больше ни на что. Любое другое программное обеспечение выполняет собственное определение возможностей и никогда не считывает эту переменную.
Ядро — это важный случай. dm-crypt и LUKS используют криптографический API ядра, и модуль aesni_intel отказывается загружаться, если бит поддержки функции процессора отсутствует:
modprobe: ERROR: could not insert 'aesni_intel': No such deviceДля этого не существует переменной пользовательского пространства. Ядро считывает CPUID один раз при загрузке, и это решение остается в силе до тех пор, пока вы не перезагрузитесь на другом хосте, поэтому ваш зашифрованный том будет использовать программный шифр независимо от того, что делает OpenSSL. Измерьте реальную производительность:
sudo cryptsetup benchmark -c aes-xts -s 256Строка aes-xts 256b показывает тысячи МиБ/с при аппаратном AES и лишь сотни без него. Среды выполнения языков программирования с собственным механизмом определения, включая Go и Java, также остаются вне зоны влияния. Пакет crypto/aes в Go напрямую проверяет CPUID и молча переключается на общую программную реализацию, если бит не установлен; документация пакета гласит, что эта реализация не является константной по времени. Если ваш TLS-терминатор — это бинарный файл на Go, переменная OpenSSL ничего для него не изменит.
Если AES-NI недоступен, используйте ChaCha20
ChaCha20-Poly1305 был разработан для высокой скорости при программной реализации. На ядре без доступного AES-NI он обычно значительно превосходит AES-GCM, поэтому разумным решением на таком хосте будет отказаться от приоритета AES.
Для nginx версии 1.19.4 и новее, собранного с OpenSSL 1.1.1 или новее:
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384;ssl_ciphers охватывает TLS 1.2. ssl_conf_command Ciphersuites охватывает TLS 1.3, где у nginx нет специальной директивы, и он передает строку в OpenSSL. nginx проверяет ответ OpenSSL: неверно написанная команда или список, в котором OpenSSL не распознает ни одного набора шифров, останавливает загрузку конфигурации с ошибкой уровня emerg SSL_CONF_cmd(...) failed. OpenSSL 3 молча пропускает одно неизвестное имя набора шифров внутри корректного списка, поэтому опечатка в одном названии может остаться незамеченной. Перезагрузите конфигурацию и проверьте, что предлагается клиенту:
sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>/dev/null | grep -i cipherКорректный результат содержит TLS_CHACHA20_POLY1305_SHA256. Прежде чем применять изменения, запустите openssl speed -evp chacha20-poly1305 рядом с тестом AES на той же машине и позвольте цифрам решить исход.
Хосты на архитектуре ARM используют другие расширения
AES-NI доступно только для x86. VPS на базе ARM использует криптографические расширения ARMv8 — отдельный набор инструкций, выполняющий те же функции. В архитектуре aarch64 флаги находятся в Features, а не в flags:
grep -m1 Features /proc/cpuinfoИщите aes и pmull. pmull является аналогом PCLMULQDQ для ARM, и GCM требует его по той же причине. Переменная переопределения OpenSSL на ARM называется OPENSSL_armcap и имеет собственную структуру битов, определённую в crypto/arm_arch.h в исходном коде OpenSSL, поэтому шестнадцатеричное значение для x86 из этого руководства там не применимо. На практике серверные ядра ARM, используемые в качестве хостов VPS, предоставляют эти расширения, поэтому проблема маскирования функций характерна в основном для архитектуры x86.
Режимы сбоев и соответствующие им сообщения
Отсутствует aes в /proc/cpuinfo, принудительный запуск выполняется значительно быстрее. Хост скрывает CPUID. Убедитесь, что имя модели является общим (generic), затем уточните у провайдера, какую модель процессора их гипервизор предоставляет гостевой системе.
Отсутствует aes в /proc/cpuinfo, принудительный запуск выводит Illegal instruction. Инструкции действительно отсутствуют или отключены на уровне прошивки. Перенесите рабочую нагрузку на другой хост.
aes присутствует, но пропускная способность всё равно низкая. Убедитесь, что вы смотрите на столбец 8192-byte и что ядро не занято другими процессами. На тарифах с общими ресурсами «шумный сосед» выглядит в точности как отсутствие функции процессора, пока вы не запустите тест дважды в разное время суток.
Маскированный и обычный запуски дают одинаковый результат. OpenSSL уже использовал программный путь выполнения. Этот результат является итогом теста, а не ошибкой.
Вложенная виртуализация меняет ответ на уровень ниже. Гостевая система внутри другой гостевой системы получает тот CPUID, который был передан промежуточным слоем, и в таких условиях легко незаметно потерять поддержку AES-NI. Если вы используете вложенные виртуальные машины на VPS, проверяйте наличие флага как внутри внутренней гостевой системы, так и на арендованной вами машине.
FAQ
Почему на моем VPS нет флага aes в /proc/cpuinfo?
Потому что гипервизор предоставляет общую модель гостевого процессора. qemu64 и kvm64 не включают AES-NI в свои наборы функций, поэтому CPUID сообщает об их отсутствии, независимо от того, какой физический процессор установлен. Хосты делают это для того, чтобы работающий гость мог быть перенесен между машинами с разными процессорами. Запустите grep -m1 'model name' /proc/cpuinfo: строка вроде QEMU Virtual CPU version 2.5+ или Common KVM processor является индикатором. Реальная модель Xeon или EPYC означает, что модель процессора пробрасывается напрямую, но эти процессоры имеют AES-NI на аппаратном уровне (спецификация Intel для Xeon Gold 6338 включает его), поэтому отсутствие флага в этом случае означает, что гипервизор скрыл этот бит функции. Принудительный запуск OPENSSL_ia32cap, описанный выше, покажет, выполняются ли инструкции на самом деле.
Действительно ли OPENSSL_ia32cap включает AES-NI или только имитирует его?
Она включает реальные инструкции. Инструкции AES-NI являются непривилегированными, и гипервизор никогда не перехватывает их, поэтому AESENC выполняется нативно, независимо от того, что сообщает CPUID. Перехватывается только инструкция CPUID. Установка OPENSSL_ia32cap в шестнадцатеричное значение заменяет ответ, который OpenSSL получил от CPUID, поэтому OpenSSL выбирает свой аппаратный путь выполнения, и оборудование работает на полной скорости. Если в процессоре действительно отсутствуют эти инструкции, процесс завершится с ошибкой Illegal instruction (core dumped) при первой же операции AES.
Ускорит ли эта переменная мой зашифрованный том LUKS?
Нет. OPENSSL_ia32cap считывается только OpenSSL. LUKS и dm-crypt используют криптографический API ядра, где модуль aesni_intel не загружается с ошибкой modprobe: ERROR: could not insert 'aesni_intel': No such device, если бит функции не установлен. Ядро считывает CPUID при загрузке, и никакая переменная пользовательского пространства это не изменит. Измерьте реальный показатель с помощью sudo cryptsetup benchmark -c aes-xts -s 256 и сравните строку aes-xts 256b с хостом, на котором этот флаг присутствует.
Замедляет ли отсутствие флага AES-NI работу WireGuard?
Нет. WireGuard использует ChaCha20-Poly1305 для всех данных и никогда не использует инструкции AES, поэтому его пропускная способность одинакова как на хосте с маскировкой, так и без нее. OpenVPN и IPsec, настроенные с AES-GCM, действительно теряют производительность на хосте без AES-NI. Таким образом, два туннеля на одном и том же VPS могут вести себя по-разному, что стоит учитывать, прежде чем винить сеть.
Как проверить наличие AES-NI на ARM VPS?
Ядра ARM не имеют AES-NI. У них есть криптографические расширения ARMv8, которые выполняют ту же задачу с помощью других инструкций. Запустите grep -m1 Features /proc/cpuinfo и найдите aes и pmull, так как в aarch64 они перечислены в Features, а не в flags. Значение OPENSSL_ia32cap для x86 не имеет смысла на ARM. Эквивалентная переменная OpenSSL там — OPENSSL_armcap, а ее битовая структура определена в crypto/arm_arch.h в исходном коде OpenSSL.