Terminaison TLS : stunnel, nginx & stud, second round

Vincent Bernat

Il y a environ un mois, j’ai publié un article comparant les performances de stunnel, nginx et stud en tant que terminaisons TLS. La conclusion était alors d’utiliser stud sur un système 64 bits, avec un cache de sessions et de l’AES. stunnel ne parvenait pas à exploiter plusieurs cœurs et nginx présentait d’importants problèmes de latence. J’ai reçu un certain nombre de commentaires sur ces tests ce qui amène une deuxième série de tests. Les protagonistes sont les mêmes mais les conditions et les conclusions diffèrent.

!!! “Mise à jour (01.2016)”stud n’est plus maintenu. Il a été repris par l’équipe de Varnish et s’appelle désormais hitch. Je vous conseille toutefois HAProxy. Il dispose désormais du support de TLS, ajouté par un contributeur majeur de stud.

Benchmarks#

La plateforme matérielle utilisée reste la même :

L’environnement logiciel est le suivant :

  • Ubuntu Lucid qui est livré avec une version modifiée OpenSSL 0.9.8k comprenant le support d’AES-NI activé par défaut.
  • Noyau Linux 2.6.39 (250 HZ)
  • 64 bits
  • 1 cœur pour HAProxy, 2 cœurs pour les cartes réseau1, 4 cœurs pour TLS, 1 cœur pour le système
  • limite de 100 000 descripteurs de fichiers par processus

Du côté TLS, les principaux facteurs sont :

  • certificats de 2048 bits
  • suite de chiffrement AES128-SHA
  • 8 requêtes HTTP/1.0 par client
  • réponse HTTP de 1024 octets

L’article précédent explore des variations de ces facteurs (32 bits, nombre de cœurs, certificats de 1024 bits, nombre de requêtes par client). Les résultats qui y sont présentés sont toujours valides. Seuls les certificats de 2048 bits sont considérés ici car c’est généralement la seule taille que délivrent désormais les autorités de certification. Dans son rapport SP800-57, le NIST indique que les clefs RSA de 1024 bits ne remplissent plus leur rôle et conseille de passer à des clefs de 2048 bits :

Si une information signée en 2009 nécessite de rester secrète jusqu’en 2019, une clé RSA de 1024 bits ne fournit pas une protection adéquate entre 2011 et 2019 et il est donc recommandé de ne pas utiliser de clefs RSA de 1024 bits dans ce cas.

Enfin, les protagonistes sont :

Mise à jour (10.2011)

La configuration de nginx a été modifiée pour passer à off les paramètres multi_accept et accept_mutex suite aux conseils avisés de Maxim Dounin sur les latences qui avaient été rencontrés lors du précédent test (et qui étaient toujours présents dans les premières versions de cet article).

stud et stunnel sont appuyés par HAProxy (configuration) pour la répartition de charge.

Pour chaque test, nous désirons maximiser le nombre de transactions HTTP par seconde (TPS) tout en minimisant les latences et les transactions abandonnées. Nous plaçons comme limite 1 transaction échouée pour 1000 transactions et une latence moyenne d’au plus 100 ms2.

Cas témoin#

Voyons comment se comportent stud, nginx et stunnel sur une Ubuntu Lucid de base. Comme lors des précédents tests, stunnel n’est pas capable de monter suffisamment en performance (plafond à 400 TPS malgré les 4 cœurs). Cependant, les latences rencontrées précédemment avec nginx ont disparu suite à l’ajustement de la configuration de ce dernier. stud et nginx présentent des performances brutes similaires mais comme on peut le voir sur le graphique du bas (dont l’échelle est logarithmique), stud commence dès le début avec une latence de 40 ms.

C’est quelque chose d’assez ennuyeux que j’ai tenté de corriger en appliquant quelques micro-optimisations mais sans succès. Lorsqu’une latence de 40 ms est considérée comme rédhibitoire, il est alors préférable de rester avec stunnel ou nginx (ou de corriger le problème dans stud).

Mise à jour (10.2011)

Une fois le problème remonté auprès de l’auteur, une correction a été publiée pour résoudre ce problème de latence. Le coupable était l’algorithme de Nagle qui permet d’augmenter la bande passante aux dépens de la latence. Avec cette correction, les performances de stud restent identiques mais la latence devient très faible.

stud, nginx, stunnel sur Ubuntu Lucid
Performances de stud, nginx et stunnel sur Ubuntu Lucid

OpenSSL 1.0.0e#

Lors de la publication de la première série de tests, Michał Trojnara a été interloqué des mauvais résultats de stunnel. Il m’a dit sur Twitter:

J’ai trouvé ! Tu as compilé stunnel avec une version d’OpenSSL vieille de 4 ans pour laquelle SSL_accept ne peut pas être utilisée de manière sûre avec des threads. Mets à jour OpenSSL et recompile stunnel. Ce bug a été corrigé dans OpenSSL 1.0.0b. J’avais mis en place un (lent) palliatif pour que stunnel ne plante pas avec les versions précédentes d’OpenSSL.

J’ai donc porté OpenSSL 1.0.0e d’Ubuntu Oneiric vers Ubuntu Lucid3 et comme il est possible de constater sur le graphique ci-dessous, stunnel béneficie d’une accélération de 400 %. stud ne gagne qu’environ 8 % tandis que nginx ne bouge pas.

stud, nginx, stunnel avec OpenSSL 1.0.0e
Performances de stud, nginx et stunnel avec OpenSSL 1.0.0e

Intel Accelerator Engine#

La plupart des distributions Linux livrent encore OpenSSL 0.9.8. Ainsi, Debian Squeeze fournit OpenSSL 0.9.8o, Ubuntu Lucid la version 0.9.8k et RHEL 5.7 la version 0.9.8e. Ces versions ne permettent pas de bénéficier des améliorations de performance obtenues ces dernières années. Pour résoudre ce problème, Intel a publié le Intel Accelerator Engine, un moteur cryptographique pour OpenSSL et apportant en autres le support d’AES-NI :

Il s’agit essentiellement d’un ensemble de modules en assembleur issus de la branche de développement d’OpenSSL et fournis sous la forme d’un moteur autonome. « Autonome » signifie qu’il est possible de les compiler en dehors de l’arbre de sources d’OpenSSL sans modifier celui-ci. L’idée est de fournir une façon d’utiliser du code récent pour des versions d’OpenSSL déjà publiées et soumises à des contraintes de support et de politique de mises à jour limitant les évolutions à des corrections de bogues.

D’après mes tests, il n’y a aucun gain à utiliser ce moteur avec OpenSSL 1.0.0e si ce dernier est déjà patché pour le support d’AES-NI, comme c’est le cas avec les versions Ubuntu.

Pour utiliser un moteur OpenSSL additionnel avec stunnel, il suffit d’ajouter quelque chose comme ceci dans stunnel.conf :

engine=dynamic
engineCtrl=SO_PATH:/WOO/intel-accel-1.4/libintel-accel.so
engineCtrl=ID:intel-accel
engineCtrl=LIST_ADD:1
engineCtrl=LOAD
engineCtrl=INIT

Pour stud, il est nécessaire d’ajouter un patch permettant la sélection du moteur OpenSSL à utiliser. Pour nginx, il suffit d’indiquer le moteur à utiliser avec la directive ssl_engine.

Intel Performance Primitives#

Dans son excellent article « Accelerated SSL », Simon Kuhn indique obtenir d’importantes améliorations de performance en utilisant la bibliothèque IPP d’Intel incluant des primitives cryptographiques ainsi qu’un patch pour les utiliser au sein d’OpenSSL. Malheureusement, celle-ci n’est pas libre (et même pas gratuite). Le patch fourni pour OpenSSL ne s’applique que sur la version 0.9.8j. Simon l’a adapté pour OpenSSL 1.0.0d. Il donne des instructions détaillées sur la compilation d’OpenSSL avec ce patch.

Simon a notamment mesuré une amélioration de 220 % sur les opérations RSA 1024 bits en utilisant cette bibliothèque. Avec stud, je n’obtiens qu’une amélioration de 15 % (mais les opérations RSA n’ont lieu qu’une fois toutes les huit connexions en raison du cache de sessions). Pire, avec stunnel, les performances sont réduites d’environ un tiers tandis que nginx ne parvient même pas à démarrer (segfault au lancement). Il semble risqué d’utiliser IPP en production.

stud et stunnel avec IPP
Performances de stud et stunnel avec la bibliothèque IPP

Hyperthreading#

Durant les précédents tests, seule la moitié de chaque cœur était exploitée. Ajouter plusieurs workers par cœur n’améliore que peu cette situation (environ 10 % pour 4 workers par cœur pour stud). Peut-être que l’utilisation de l’hyperthreading peut aider ici ? Quand ce dernier est activé, stud n’obtient qu’une amélioration de 5 %, nginx rattrape son retard sur stud avec une amélioration de 11 % et stunnel augmente ses performances de plus de 26 % !

stud, stunnel et nginx avec hyperthreading
Performances de stud, stunnel et nginx avec hyperthreading

Conclusion#

Voici un récapitulatif de l’ensemble des résultats collectés. Rappelons que la mesure retenue est le maximum de TPS tout en maintenant un temps moyen de réponse sous la barre des 100 ms. Tous les tests ont été effectués avec quatre cœurs physiques, des certificates de 2048 bits et de l’AES-SHA1. Chaque client effectue huit requêtes HTTP et réutilise la première session TLS.

Context nginx 1.1.4 stunnel 4.44 stud @e207dbc5a3
OpenSSL 0.9.8k 1489 TPS 400 TPS 1492 TPS
OpenSSL 1.0.0e 1501 TPS 1561 TPS 1609 TPS
OpenSSL 1.0.0e + IPP - 1101 TPS 1857 TPS
OpenSSL 1.0.0e + HT 1718 TPS 1973 TPS 1694 TPS

Il est également important de souligner que sous 1500 TPS, stunnel ajoute une latence de quelques millisecondes seulement, là où stud ajoute 40 millisecondes. Ainsi, avec OpenSSL 1.0.0e et l’hyperthreading, stunnel permet d’obtenir à la fois de bonnes performances et une latence faible sur des charges faibles ou moyennes. Il convient simplement de limiter le nombre de connexions pour éviter qu’il ne s’écroule sous forte charge. Alors que lors du précédent round, nginx sortait en champion et stunnel traînait derrière, la situation est désormais inversée : stunnel tient le haut du pavé !

Mise à jour (12.2011)

Comme indiqué précédemment, les problèmes de latence rencontrés avec stud ont été résolu. Le nombre de TPS ne varie pas. Pour comparaison, voici à quoi ressemble le précédent graphique avec le correctif adéquat appliqué sur stud :

stud, stunnel et nginx avec hyperthreading et correctif pour stud
Performances de stud, stunnel et nginx avec hyperthreading et algorithme de Nagle désactivé pour stud

  1. La charge induite par les transactions TLS est trop faible pour avoir à dédier des cœurs pour HAProxy et les cartes réseau. Cependant, si la machine doit aussi faire transiter du trafic classique, cette configuration peut venir à point nommé. ↩︎

  2. La véritable condition est plus complexe que cela. Un point est considéré comme répondant à nos conditions si la latence moyenne correspondante est inférieure à 100 ms ou si la moyenne sur les 8 points suivants est inférieure à 100 ms. Cela permet d’absorber les pics de latence temporaires. Nous ne sommes pas aussi cléments avec les connexions qui échouent. ↩︎

  3. Ce n’est pas très compliqué : il suffit de retirer le support multiarch. J’ai opté pour la version d’OpenSSL présente dans Ubuntu plutôt que la version disponible sur le site officiel afin de bénéficier notamment du support de l’AES-NI. ↩︎