Linux Kernel TLS и Nginx
В этой статье я расскажу об истории развития и текущем состоянии технологии ускорения раздачи контента в TLS соединениях путем переноса шифрования в ядро операционной системы, а так же о своём вкладе в развитие этого направления.
Предыстория
В далеком 2015 году Randall Stewart и Scott Long из компании Netflix выступили на конференции AsiaBSDCon2015 c докладом об оптимизации раздачи шифрованного контена. Основной посыл доклада — необходимо переносить шифрование данных в ядро операционной системы для уменьшения количества копирований данных между kernel-space и user-space и использовать оптимизированный неблокирующий системный вызов sendfile (). Тема оказалась очень перспективной и уже в 2016 году на конференции Netdev 1.2 Dave Watson из Facebook выступил с докладом о необходимости создания TLS сокетов в ядре Linux, а Boris Pismenny, Ilya Lesokhin и Liran Liss из Mellanox — с докладом о возможности использовать аппаратное ускорение TLS в сетевых картах. Так же тема обсуждалась на HighLoad++ 2017 докладчиком от Tempesta Technologies.
Поддержка в ядре Linux
Итогом всех этих разговоров стало появление Kernel TLS в ядре Linux 4.13 (2017 год) c поддержкой TLSv1.2 и шифра AES128-GCM. Первоначально поддерживалось шифрование только исходящего трафика, поддержка дешифрования появилась позже в ядре Linux 4.17 (2018 год). В версии 5.1 добавили поддержку TLSv1.3 и AES256-GCM, а в версии 5.2 еще и шифр AES128-CCM (2019 год).
Поддержка в user-space
Во всех указанных выше докладах рассказывалось, что в ядро можно поместить только шифрование полезных данных, а все согласования и контрольные сообщения TLS необходимо обрабатывать всё так же в user-space. И для этого использовалась модифицированная версия OpenSSL. Однако на момент публикации докладов в открытом доступе не было информации о том, какие именно модификации в эту широкоизвестную библиотеку надо сделать, чтобы поддержать функционал. Пожалуй, единственным доступным примером использования Linux Kernel TLS была статья в блоге Filippo Valsorda Playing with kernel TLS in Linux 4.13 and Go, появившаяся сразу после выхода ядра Linux 4.13. И, хотя она показывала действительный пример использования технологии, это не принесло понимания в то, как же использовать технологию в реальных проектах. Ведь очень мало кто самостоятельно пишет WEB-сервер для своего проекта, обычно все используют известные и проверенные временем инструменты.
Поддержка в OpenSSL
Первые обсуждения технологии в OpenSSL появились летом 2017 года незадолго до выхода ядра Linux 4.13 (PR 3631), однако процесс обсуждения шел очень и очень медленно, первые реальные замечания появились в октябре (уже после выхода ядра 4.13), а собственно рабочий вариант начали обсуждать в феврале 2018. К моменту согласования всех исправлений, окно добавления нового функционала в OpenSSL 1.1.1 было закрыто, и поддержку Kernel TLS перенесли в следующий релиз. С того времени добавили поддежку Kernel TLS RX, и SSL_sendfile () — вызов соответствующего syscall с небольшой обработкой возможных ситуаций в протоколе TLS. Но сейчас на дворе 2020 год, OpenSSL 3.0 еще не вышел, TLSv1.3 поддерживается подавляющим большинством браузеров, а шифр AES128-GCM активно вытесняется более стойким AES256-GCM. Поэтому я взял на себя смелость и отправил Pull Request на поддержку новых шифров и TLSv1.3 в надежде, что его примут до нового релиза библиотеки.
Поддержка в WEB-серверах
Но поддержать Kernel TLS в TLS библиотеке мало. В докладах про технологию говорилось, что максимальную эффективность можно получить используя отправку без копирования данных в user-space — используя системный вызов sendfile (). И серверное приложение должно уметь различать ситуации, когда можно использовать sendfile () на сокете с TLS, а когда необходимо «по-старинке» делать read ()/SSL_write (). Некоторые подвижки в сторону добавления функционала в Nginx появились в апреле 2019 года, но в основной код изменения приняты не были. Позиция разработчиков в том, что API для этих функиций в OpenSSL еще не утвержден, а собственно сам код, предложенный в патче, не выглядит достаточно портируемым на разные платформы. Скажу честно, код не только выглядит не очень красиво, но и содержит ошибки, которые не позволяют собрать Nginx без дополнительных исправлений. Поддержки Kernel TLS в других веб-серверах я вообще не смог найти (может быть кто-то видел — подскажите в комментах).
И что же делать?
Пока сообщество ожидает выхода релиза OpenSSL 3.0, чтобы начать разработку поддержки Kernel TLS в Nginx в условиях стабильного API, я пошел иным путём. В своём уютном уголочке GitHub я сделал 2 вещи:
- Создал форк OpenSSL и бэкпортировал всё связанное с Kernel TLS в стабильную версию OpenSSL 1.1.1 (ветка OpenSSL_1_1_1-ktls). Специально для того, чтобы иметь возможность проверять функционал в условиях стабильной работы остальной части библиотеки. По мере выхода стабильных версий стараюсь делать rebase, чтобы форк был актуальным.
- Создал форк Nginx, в который добавил (на основе патча от Mellanox) поддержку вызова SSL_sendfile () в условиях, когда это действительно возможно и с необходимыми проверками SSL сокета, и возможность включения/выключения функционала через конфигурационную переменную. Помимо этой фичи в моем форке так же есть несколько патчей, немного оптимизирующих работу Nginx, и исправляющих некоторые баги (ветка master-feature). По мере возможностей я стараюсь делать rebase на основе master-ветки основного репо Nginx, чтобы поддерживать форк в условно-актуальном состоянии.
Предлагаю присоединиться к тестированию всех желающих. Замечания и исправления к коду можно кидать в Issues в GitHub. Собрать этот Nginx с этим OpenSSL и включенной поддержкой Kernel TLS можно добавив параметры к скрипту configure:
./configure --with-openssl= --with-openssl-opt="enable-ktls"
В настоящий момент у меня нет возможности протестировать эту связку Nginx + OpenSSL под серьезной нагрузкой, чтобы подтвердить показатели из коммента к патчу Nginx, поэтому если вдруг кто сможет замерить разницу в нагрузке на CPU на больших скоростях отдачи файлов — было бы здорово получить графики и добавить их в статью для визуального понимания эффекта.