كيف تتحقق من AES-NI وتستعيده على VPS؟
تحقق من ظهور AES-NI على VPS، وقِس كلفة إخفاء CPUID على أداء AES-GCM، ثم أعد تفعيل التعليمات عبر OPENSSL_ia32cap.
ما الذي يقدمه AES-NI فعلياً على VPS
AES-NI هو مجموعة من ستة تعليمات x86 تنفّذ جولة واحدة من AES (معيار التشفير المتقدم) في العتاد. إذا أخفى طراز CPU الذي يقدمه مزوّد الخدمة هذه التعليمات، فستظل موجودة في الشريحة نفسها، لكن OpenSSL لن يتمكن من رؤيتها، وسينتقل إلى تنفيذ برمجي يستهلك نحو عشرة أضعاف عدد الدورات لكل بايت. يمكنك التحقق من توفر الميزة باستخدام أمر واحد، وقياس الفارق باستخدام أمرين، وغالباً إعادة تفعيل المسار السريع عبر متغير بيئة واحد.
التعليمات هي AESENC وAESENCLAST وAESDEC وAESDECLAST وAESIMC وAESKEYGENASSIST. أطلقت Intel هذه التعليمات في 2010، ثم لحقتها AMD، لذلك تحتوي شريحة CPU في أي خادم يُرجح أن تستأجره على هذه التعليمات. وتنفّذ التعليمات المرافقة PCLMULQDQ عملية ضرب دون حمل، وهي العملية التي يحتاج إليها GCM (وضع Galois/counter) لإنشاء وسم المصادقة. لا يكون AES-GCM سريعاً إلا عند توفر التعليماتين معاً، لأن التشفير وإنشاء الوسم عمليتان منفصلتان.
تظهر هذه الميزة في أربعة مواضع على VPS ويمكنك ملاحظتها في المراقبة:
- إنهاء TLS (أمان طبقة النقل). يقضي خادم الويب الذي يقدّم AES-128-GCM أو AES-256-GCM معظم وقت التشفير الكتلي داخل AES.
- وحدات التخزين المشفّرة. يشغّل LUKS (إعداد المفتاح الموحّد لنظام Linux) وdm-crypt
aes-xtsمع كل عملية قراءة وكتابة، داخل kernel وعلى CPU. - حركة VPN المعتمدة على AES. يعتمد OpenVPN مع
AES-256-GCMوIPsec مع AES-GCM عليهما بدرجة كبيرة. - النسخ الاحتياطية المشفّرة. أي برنامج يشفّر تدفقاً باستخدام AES قبل خروجه من الخادم يتحمل التكلفة نفسها.
لا يتأثر أحد أحمال العمل الشائعة بهذه الميزة إطلاقاً. يستخدم WireGuard ChaCha20-Poly1305 لبياناته ولا يستخدم AES مطلقاً، لذلك يعمل VPN WireGuard المستضاف ذاتياً بالسرعة نفسها على مضيف يخفي هذا العلم. ويوفر هذا الاختلاف سبباً عملياً للموازنة بين WireGuard وOpenVPN قبل اختيار نفق لـVPS منخفض التكلفة.
كيفية التحقق من أن VPS لديك يدعم AES-NI
ينسخ kernel بتات ميزات 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 دائماً إلى اعتراض من hypervisor، لذلك يحدد hypervisor ما يُبلَّغ به الضيف. تعرض معظم لوحات التحكم هذا القرار على شكل نموذج CPU للضيف. يُعد qemu64 وkvm64 نموذجي خط أساس عامين، ولا يتضمن أي منهما AES-NI أو SSSE3 ضمن مجموعة الميزات، لذلك لا يرى الضيف علامة aes حتى عندما يكون المضيف الفعلي بمعالج EPYC حديث. إن VPS ضيف على عتاد يملكه طرف آخر، لذلك فإن كل ميزة يبلّغ عنها هي قرار اتُّخذ في مستوى أعلى. إذا كان هذا الفصل بين الطبقات جديداً عليك، فابدأ بقراءة ما هو VPS.
تختار المضيفات نموذجاً عاماً عن قصد، لأن الترحيل المباشر بين أجهزة ذات معالجات مختلفة لا يعمل إلا إذا لم يُبلَّغ الضيف مسبقاً عن ميزة يفتقر إليها الجهاز الهدف. وتتحمل أنت تكلفة ذلك. يقرأ كل من kernel ونسختك من OpenSSL قيمة CPUID المحجوبة مرة واحدة عند بدء التشغيل، ثم يختار كل منهما مسار التعليمات البرمجية البطيء طوال مدة العملية.
يكمن الإصلاح من المصدر في إعداد على المضيف: -cpu host وفق مصطلحات QEMU، أو نموذج مسمّى يتضمن AES-NI، أو +aes صريحة تُضاف إلى النموذج. لا يمكنك ضبط أي من ذلك من داخل الضيف. إن فتح تذكرة دعم، أو اختيار خطة يمرر hypervisor فيها نموذج CPU إلى الضيف، هو الحل المستدام.
قِس الفارق باستخدام 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 كان يستخدم مسار التنفيذ البرمجي أصلاً، لأن الراية لم تكن موجودة لمسحها.
The data behind this chart
[
{
"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 GHz، وليست قياساً مأخوذاً من خادم محدد. اقرأها بوصفها نمطاً عاماً. يعمل مسار العتاد بنحو 0.7 دورة لكل بايت، بينما يعمل المسار البرمجي الاحتياطي بنحو 11.0، أي ما يعادل نحو 4,850 MB/s مقابل 310 MB/s على نواة واحدة. يعطيك الأمران أعلاه الرقم الوحيد الذي يصف خادمك. وينطبق المبدأ نفسه على بقية الجهاز، لذلك اربط هذا بـطريقة قابلة للتكرار لقياس أداء 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 leaf 1 في أقل 32 بت، وقيمة ECX من leaf 1 في أعلى 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 خطأ تعليمة غير معرّفة، وتنتهي العملية.
يؤدي فرض تفعيل 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 بسبب تعليمة غير قانونية عند أول اتصال TLS. أضف ذلك إلى runbook، أو اترك التجاوز خارج بيئة الإنتاج تماماً، واستخدمه فقط لإثبات المشكلة عند فتح تذكرة.
ما الذي لا يصلحه override
يصل OPENSSL_ia32cap إلى OpenSSL فقط، ولا يصل إلى أي برنامج آخر. يجري كل برنامج آخر اكتشاف الميزات الخاص به، ولا يقرأ هذا المتغير مطلقاً.
النواة هي الحالة المهمة. يستخدم dm-crypt وLUKS واجهة التشفير في النواة، وترفض الوحدة 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 إلى آلاف MiB/s عند استخدام 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 من دون التحقق منها. لذلك، يُقبل الخطأ المطبعي هناك بصمت. أعد التحميل وتحقق مما يُعرض على العميل:
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 النظير الخاص بـARM من PCLMULQDQ، ويحتاج GCM إليه للسبب نفسه. ومتغير التجاوز في OpenSSL على ARM هو OPENSSL_armcap، مع تخطيط بتات خاص به معرّف في crypto/arm_arch.h ضمن مصدر OpenSSL، لذلك لا تعني قيمة hexadecimal الخاصة بـx86 الواردة في هذا الدليل شيئاً على ARM. عملياً، تعرض نوى خوادم ARM المباعة كمضيفات VPS هذه الامتدادات، ولذلك ترتبط مشكلة الميزات المحجوبة غالباً بمعمارية x86.
أنماط الفشل، مع النصوص التي ستظهر لك
لا يظهر aes في /proc/cpuinfo، ويكون التشغيل القسري أسرع بكثير. يخفي المضيف معلومات CPUID. تأكد من أن اسم الطراز عاماً، ثم اسأل مزود الخدمة عن طراز CPU الضيف الذي يقدمه برنامج hypervisor لديه.
لا يظهر aes في /proc/cpuinfo، ويعرض التشغيل القسري Illegal instruction. التعليمات غير متاحة فعلياً، أو عطّلها البرنامج الثابت. انقل عبء العمل إلى مضيف آخر.
يوجد aes، لكن معدل النقل لا يزال منخفضاً. تحقق من أنك تقرأ عمود 8192 بايت، ومن عدم استخدام أي شيء آخر للنواة. في الخطط المشتركة، يبدو الجار المزعج تماماً مثل ميزة CPU مفقودة إلى أن تشغّل الاختبار مرتين في وقتين مختلفين من اليوم.
يعطي التشغيل المقنّع والتشغيل العادي الرقم نفسه. كان OpenSSL يستخدم مسار البرامج بالفعل. هذه النتيجة هي ما يجب اكتشافه، وليست خطأً في الاختبار.
تغيّر المحاكاة الافتراضية المتداخلة الإجابة في المستوى الأدنى. يحصل الضيف داخل ضيف على معلومات CPUID التي اختارت الطبقة الوسطى تمريرها، ومن السهل فقدان AES-NI هناك دون ملاحظة ذلك. إذا كنت تشغّل آلات افتراضية متداخلة على VPS، فتحقق من العلم داخل الضيف الداخلي وكذلك على الجهاز الذي استأجرته.
FAQ
لماذا لا يحتوي VPS لدي على العلامة aes في /proc/cpuinfo؟
لأن hypervisor يعرض نموذج CPU عاماً للضيف. لا يتضمن qemu64 وkvm64 AES-NI في مجموعات الميزات الخاصة بهما، لذلك يعرض CPUID أنها غير موجودة مهما كان المعالج الفعلي. تفعل المضيفات ذلك حتى يمكن نقل ضيف قيد التشغيل بين أجهزة ذات معالجات مختلفة. شغّل grep -m1 'model name' /proc/cpuinfo: إذ تكون سلسلة مثل QEMU Virtual CPU version 2.5+ أو Common KVM processor دليلاً واضحاً، بينما تعني سلسلة نموذج Xeon أو EPYC فعلية أن نموذج CPU مُمرَّر مباشرة وأن العلامة مفقودة فعلاً من العتاد.
هل يفعّل OPENSSL_ia32cap AES-NI فعلاً، أم يتظاهر بذلك فقط؟
إنه يفعّل التعليمات الفعلية. تعليمات AES-NI غير مميّزة، ولا يعترض hypervisor تنفيذها، لذلك ينفّذ AESENC التعليمات محلياً بغض النظر عما يعرضه CPUID. لا تتمّ اعتراضات إلا على تعليمة CPUID. يؤدي ضبط OPENSSL_ia32cap على قيمة سداسية عشرية عادية إلى استبدال الإجابة التي حصل عليها OpenSSL من CPUID، لذلك يختار OpenSSL مسار تنفيذ العتاد، ثم ينفّذه العتاد بالسرعة الكاملة. إذا كان العتاد يفتقر فعلاً إلى هذه التعليمات، تنتهي العملية بالخطأ Illegal instruction (core dumped) عند أول عملية AES.
هل يؤدي هذا التجاوز إلى تسريع وحدة التخزين المشفّرة بواسطة LUKS؟
لا. يقرأ OpenSSL OPENSSL_ia32cap ولا يقرأه أي مكوّن آخر. يستخدم LUKS وdm-crypt واجهة تشفير kernel، حيث تفشل الوحدة aesni_intel في التحميل بالخطأ modprobe: ERROR: could not insert 'aesni_intel': No such device عندما تكون بتّ الميزة غير مفعّلة. يقرأ kernel قيمة 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 على VPS يعمل بمعمارية ARM؟
لا تحتوي أنوية 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.