قصة عن حزم DNS المفقودة من الدعم الفني لـ Google Cloud

من محرر مدونة جوجل: هل تساءلت يومًا كيف يتعامل مهندسو Google Cloud Technical Solutions (TSE) مع طلبات الدعم الخاصة بك؟ يتحمل مهندسو الدعم الفني في TSE مسؤولية تحديد وتصحيح مصادر المشكلات التي أبلغ عنها المستخدم. بعض هذه المشاكل بسيطة للغاية، ولكن في بعض الأحيان تصادفك تذكرة تتطلب اهتمام العديد من المهندسين في وقت واحد. في هذه المقالة، سيخبرنا أحد موظفي بورصة طوكيو عن مشكلة صعبة للغاية من ممارسته الأخيرة - حالة فقدان حزم DNS. في هذه القصة سنرى كيف تمكن المهندسون من حل الموقف، وما هي الأشياء الجديدة التي تعلموها أثناء إصلاح الخطأ. نأمل ألا تؤدي هذه القصة إلى تثقيفك حول خطأ عميق الجذور فحسب، بل ستمنحك أيضًا نظرة ثاقبة حول العمليات التي تدخل في تقديم تذكرة دعم باستخدام Google Cloud.

قصة عن حزم DNS المفقودة من الدعم الفني لـ Google Cloud

استكشاف الأخطاء وإصلاحها هو علم وفن. يبدأ كل شيء ببناء فرضية حول سبب السلوك غير القياسي للنظام، وبعد ذلك يتم اختبار قوته. ومع ذلك، قبل أن نقوم بصياغة فرضية، يجب علينا أن نحدد المشكلة بوضوح وصياغتها بدقة. إذا كان السؤال يبدو غامضا للغاية، فسيتعين عليك تحليل كل شيء بعناية؛ هذا هو "فن" استكشاف الأخطاء وإصلاحها.

في ظل Google Cloud، تصبح مثل هذه العمليات أكثر تعقيدًا بشكل كبير، حيث تبذل Google Cloud قصارى جهدها لضمان خصوصية مستخدميها. ولهذا السبب، لا يتمتع مهندسو TSE بإمكانية الوصول لتحرير أنظمتك، ولا القدرة على عرض التكوينات على نطاق واسع كما يفعل المستخدمون. لذلك، لاختبار أي من فرضياتنا، لا يمكننا نحن (المهندسين) تعديل النظام بسرعة.

يعتقد بعض المستخدمين أننا سنصلح كل شيء مثل الميكانيكيين في خدمة السيارات، ونرسل لنا ببساطة معرف الجهاز الظاهري، بينما في الواقع تتم العملية بتنسيق محادثة: جمع المعلومات، وتشكيل وتأكيد (أو دحض) الفرضيات، وفي النهاية، يعتمد قرار المشكلات على التواصل مع العميل.

المشكلة في السؤال

اليوم لدينا قصة مع نهاية جيدة. أحد أسباب الحل الناجح للحالة المقترحة هو الوصف التفصيلي والدقيق للغاية للمشكلة. يمكنك أدناه رؤية نسخة من التذكرة الأولى (تم تعديلها لإخفاء المعلومات الحساسة):
قصة عن حزم DNS المفقودة من الدعم الفني لـ Google Cloud
تحتوي هذه الرسالة على الكثير من المعلومات المفيدة لنا:

  • تم تحديد جهاز افتراضي محدد
  • يشار إلى المشكلة نفسها - DNS لا يعمل
  • يشار إلى مكان ظهور المشكلة - VM والحاوية
  • تتم الإشارة إلى الخطوات التي اتخذها المستخدم لتحديد المشكلة.

تم تسجيل الطلب تحت عنوان "P1: Critical Impact - Service Unusable in production"، مما يعني مراقبة مستمرة للوضع على مدار الساعة طوال أيام الأسبوع وفقًا لمخطط "Follow the Sun" (يمكنك قراءة المزيد عن أولويات طلبات المستخدمين)، مع نقلها من فريق دعم فني إلى آخر مع كل تغيير في المنطقة الزمنية. في الواقع، بحلول الوقت الذي وصلت فيه المشكلة إلى فريقنا في زيوريخ، كانت قد حلقت حول العالم بالفعل. وبحلول هذا الوقت، كان المستخدم قد اتخذ تدابير تخفيفية، لكنه كان خائفًا من تكرار الوضع في الإنتاج، حيث لم يتم اكتشاف السبب الجذري بعد.

بحلول الوقت الذي وصلت فيه التذكرة إلى زيوريخ، كانت لدينا بالفعل المعلومات التالية:

  • محتوى /etc/hosts
  • محتوى /etc/resolv.conf
  • إنتاج iptables-save
  • تم تجميعها من قبل الفريق ngrep ملف pcap

باستخدام هذه البيانات، أصبحنا مستعدين لبدء مرحلة "التحقيق" واستكشاف الأخطاء وإصلاحها.

خطواتنا الأولى

أولاً، قمنا بفحص السجلات وحالة خادم البيانات الوصفية وتأكدنا من أنه يعمل بشكل صحيح. يستجيب خادم البيانات التعريفية لعنوان IP 169.254.169.254، ومن بين أمور أخرى، يكون مسؤولاً عن التحكم في أسماء النطاقات. لقد تحققنا أيضًا جيدًا من أن جدار الحماية يعمل بشكل صحيح مع الجهاز الافتراضي ولا يحظر الحزم.

لقد كانت مشكلة غريبة نوعًا ما: لقد دحض فحص nmap فرضيتنا الرئيسية حول فقدان حزم UDP، لذلك توصلنا عقليًا إلى عدة خيارات وطرق أخرى للتحقق منها:

  • هل يتم إسقاط الحزم بشكل انتقائي؟ => تحقق من قواعد iptables
  • أليست صغيرة جدًا؟ MTU؟ => التحقق من الإخراج ip a show
  • هل تؤثر المشكلة على حزم UDP فقط أو TCP أيضًا؟ => ابتعد dig +tcp
  • هل تم إرجاع الحزم التي تم إنشاؤها بواسطة الحفر؟ => ابتعد tcpdump
  • هل يعمل libdns بشكل صحيح؟ => ابتعد strace للتحقق من إرسال الحزم في كلا الاتجاهين

هنا نقرر الاتصال بالمستخدم لاستكشاف المشكلات وإصلاحها مباشرةً.

أثناء المكالمة يمكننا التحقق من عدة أشياء:

  • بعد عدة عمليات فحص، قمنا باستبعاد قواعد iptables من قائمة الأسباب
  • نحن نتحقق من واجهات الشبكة وجداول التوجيه، ونتأكد مرة أخرى من صحة وحدة الإرسال الكبرى (MTU).
  • نكتشف ذلك dig +tcp google.com (TCP) يعمل كما ينبغي، ولكن dig google.com (UDP) لا يعمل
  • بعد أن طردت بعيدا tcpdump أثناء العمل digنجد أنه يتم إرجاع حزم UDP
  • نحن نبتعد strace dig google.com ونحن نرى كيف يدعو حفر بشكل صحيح sendmsg() и recvms()، ولكن تمت مقاطعة الثانية بسبب انتهاء المهلة

لسوء الحظ، وصلت نهاية التحول واضطررنا إلى تصعيد المشكلة إلى المنطقة الزمنية التالية. ومع ذلك، أثار الطلب اهتمام فريقنا، واقترح أحد الزملاء إنشاء حزمة DNS الأولية باستخدام وحدة Python.

from scapy.all import *

answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())

يقوم هذا الجزء بإنشاء حزمة DNS ويرسل الطلب إلى خادم البيانات التعريفية.

يقوم المستخدم بتشغيل الرمز، ويتم إرجاع استجابة DNS، ويستقبلها التطبيق، مما يؤكد عدم وجود مشكلة على مستوى الشبكة.

وبعد "رحلة أخرى حول العالم"، يعود الطلب إلى فريقنا، وأنا أنقله بالكامل إلى نفسي، معتقدًا أنه سيكون أكثر ملاءمة للمستخدم إذا توقف الطلب عن الدوران من مكان إلى آخر.

وفي هذه الأثناء، يوافق المستخدم على تقديم لقطة من صورة النظام. هذه أخبار جيدة جدًا: القدرة على اختبار النظام بنفسي تجعل عملية استكشاف الأخطاء وإصلاحها أسرع بكثير، لأنني لم أعد مضطرًا إلى مطالبة المستخدم بتشغيل الأوامر وإرسال النتائج لي وتحليلها، يمكنني فعل كل شيء بنفسي!

بدأ زملائي يحسدونني قليلاً. أثناء الغداء، ناقشنا عملية التحويل، لكن لا أحد لديه أي فكرة عما يحدث. لحسن الحظ، اتخذ المستخدم نفسه بالفعل تدابير للتخفيف من العواقب وهو ليس في عجلة من أمره، لذلك لدينا الوقت لتحليل المشكلة. وبما أن لدينا صورة، يمكننا إجراء أي اختبارات تهمنا. عظيم!

أخذ خطوة إلى الوراء

أحد أسئلة المقابلة الأكثر شيوعًا لوظائف مهندسي الأنظمة هو: "ماذا يحدث عند تنفيذ الأمر ping؟ www.google.com؟ السؤال رائع، حيث يحتاج المرشح إلى وصف كل شيء بدءًا من الغلاف إلى مساحة المستخدم، إلى نواة النظام ثم إلى الشبكة. ابتسمت: في بعض الأحيان تكون أسئلة المقابلة مفيدة في الحياة الواقعية...

قررت تطبيق سؤال الموارد البشرية هذا على مشكلة حالية. بشكل تقريبي، عند محاولة تحديد اسم DNS، يحدث ما يلي:

  1. يستدعي التطبيق مكتبة النظام مثل libdns
  2. يتحقق libdns من تكوين النظام الذي يجب أن يتصل به خادم DNS (في الرسم التخطيطي هذا هو 169.254.169.254، خادم البيانات الوصفية)
  3. يستخدم libdns مكالمات النظام لإنشاء مقبس UDP (SOKET_DGRAM) وإرسال حزم UDP مع استعلام DNS في كلا الاتجاهين
  4. من خلال واجهة sysctl، يمكنك تكوين حزمة UDP على مستوى النواة
  5. تتفاعل النواة مع الأجهزة لإرسال الحزم عبر الشبكة عبر واجهة الشبكة
  6. يلتقط برنامج Hypervisor الحزمة ويرسلها إلى خادم البيانات التعريفية عند الاتصال بها
  7. يحدد خادم البيانات الوصفية، بسحره، اسم DNS ويعيد الاستجابة باستخدام نفس الطريقة

قصة عن حزم DNS المفقودة من الدعم الفني لـ Google Cloud
اسمحوا لي أن أذكركم بالفرضيات التي تناولناها بالفعل:

الفرضية: مكتبات مكسورة

  • الاختبار 1: قم بتشغيل strace في النظام، وتأكد من أن dig يستدعي مكالمات النظام الصحيحة
  • النتيجة: يتم استدعاء مكالمات النظام الصحيحة
  • الاختبار 2: استخدام srapy للتحقق مما إذا كان بإمكاننا تحديد الأسماء التي تتجاوز مكتبات النظام
  • النتيجة: نستطيع
  • الاختبار 3: تشغيل rpm –V على حزمة libdns وملفات مكتبة md5sum
  • النتيجة: رمز المكتبة مطابق تمامًا للكود الموجود في نظام التشغيل العامل
  • الاختبار 4: تحميل صورة نظام الجذر الخاص بالمستخدم على جهاز افتراضي بدون هذا السلوك، وتشغيل chroot، ومعرفة ما إذا كان DNS يعمل أم لا
  • النتيجة: DNS يعمل بشكل صحيح

الاستنتاج على أساس الاختبارات: المشكلة ليست في المكتبات

الفرضية: هناك خطأ في إعدادات DNS

  • الاختبار 1: تحقق من tcpdump وتأكد من إرسال حزم DNS وإعادتها بشكل صحيح بعد تشغيل dig
  • النتيجة: يتم إرسال الحزم بشكل صحيح
  • الاختبار 2: التحقق مرة أخرى من الخادم /etc/nsswitch.conf и /etc/resolv.conf
  • النتيجة: كل شيء صحيح

الاستنتاج على أساس الاختبارات: المشكلة ليست في تكوين DNS

الفرضية: الأساسية التالفة

  • الاختبار: تثبيت نواة جديدة، التحقق من التوقيع، إعادة التشغيل
  • النتيجة: سلوك مماثل

الاستنتاج على أساس الاختبارات: النواة غير تالفة

الفرضية: سلوك غير صحيح لشبكة المستخدم (أو واجهة شبكة برنامج Hypervisor)

  • الاختبار 1: التحقق من إعدادات جدار الحماية لديك
  • النتيجة: يقوم جدار الحماية بتمرير حزم DNS على كل من المضيف وGCP
  • الاختبار 2: اعتراض حركة المرور ومراقبة صحة إرسال وإرجاع طلبات DNS
  • النتيجة: يؤكد tcpdump أن المضيف قد تلقى حزم الإرجاع

الاستنتاج على أساس الاختبارات: المشكلة ليست في الشبكة

الفرضية: خادم البيانات التعريفية لا يعمل

  • الاختبار 1: التحقق من سجلات خادم البيانات التعريفية بحثًا عن الحالات الشاذة
  • النتيجة: لا توجد حالات شاذة في السجلات
  • الاختبار 2: تجاوز خادم البيانات التعريفية عبر dig @8.8.8.8
  • النتيجة: تم كسر الدقة حتى بدون استخدام خادم بيانات التعريف

الاستنتاج على أساس الاختبارات: المشكلة ليست مع خادم البيانات الوصفية

وخلاصة القول: اختبرنا جميع الأنظمة الفرعية باستثناء إعدادات وقت التشغيل!

الغوص في إعدادات وقت تشغيل Kernel

لتكوين بيئة تنفيذ kernel، يمكنك استخدام خيارات سطر الأوامر (grub) أو واجهة sysctl. نظرت في /etc/sysctl.conf وفكر فقط، لقد اكتشفت العديد من الإعدادات المخصصة. شعرت كما لو أنني تمسكت بشيء ما، وتجاهلت جميع الإعدادات غير المتصلة بالشبكة أو غير الخاصة بـ TCP، وبقيت مع الإعدادات الجبلية net.core. ثم ذهبت إلى حيث توجد أذونات المضيف في الجهاز الافتراضي وبدأت في تطبيق الإعدادات واحدًا تلو الآخر، واحدًا تلو الآخر، باستخدام الجهاز الافتراضي المعطل، حتى وجدت الجاني:

net.core.rmem_default = 2147483647

ومن هنا، تكوين كسر DNS! لقد وجدت سلاح الجريمة. ولكن لماذا يحدث هذا؟ ما زلت بحاجة إلى الدافع.

يتم تكوين حجم المخزن المؤقت لحزمة DNS الأساسي عبر net.core.rmem_default. تبلغ القيمة النموذجية حوالي 200 كيلو بايت، ولكن إذا كان الخادم الخاص بك يتلقى الكثير من حزم DNS، فقد ترغب في زيادة حجم المخزن المؤقت. إذا كان المخزن المؤقت ممتلئًا عند وصول حزمة جديدة، على سبيل المثال لأن التطبيق لا يعالجها بالسرعة الكافية، فسوف تبدأ في فقدان الحزم. قام عميلنا بزيادة حجم المخزن المؤقت بشكل صحيح لأنه كان خائفًا من فقدان البيانات، لأنه كان يستخدم تطبيقًا لجمع المقاييس من خلال حزم DNS. القيمة التي حددها كانت الحد الأقصى الممكن: 231-1 (إذا تم ضبطها على 231، فسوف تُرجع النواة "وسيطة غير صالحة").

فجأة أدركت سبب عمل nmap وscapy بشكل صحيح: لقد كانا يستخدمان مآخذ توصيل خام! تختلف المقابس الأولية عن المقابس العادية: فهي تتجاوز iptables ولا يتم تخزينها مؤقتًا!

ولكن لماذا يسبب "المخزن المؤقت الكبير جدًا" مشاكل؟ من الواضح أنه لا يعمل على النحو المنشود.

في هذه المرحلة، يمكنني إعادة إنتاج المشكلة على عدة حبات وتوزيعات متعددة. ظهرت المشكلة بالفعل على النواة 3.x والآن ظهرت أيضًا على النواة 5.x.

في الواقع، عند بدء التشغيل

sysctl -w net.core.rmem_default=$((2**31-1))

توقف DNS عن العمل.

بدأت البحث عن قيم العمل من خلال خوارزمية بحث ثنائية بسيطة ووجدت أن النظام يعمل مع 2147481343، لكن هذا الرقم كان بمثابة مجموعة أرقام لا معنى لها بالنسبة لي. اقترحت على العميل تجربة هذا الرقم، فأجاب بأن النظام يعمل مع google.com، لكنه لا يزال يظهر خطأ مع النطاقات الأخرى، لذلك واصلت التحقيق.

لقد ركبت dropwatch، وهي أداة كان من المفترض استخدامها سابقًا: فهي تُظهر بالضبط المكان الذي تنتهي فيه الحزمة في النواة. وكان الجاني الوظيفة udp_queue_rcv_skb. لقد قمت بتنزيل مصادر النواة وأضفت القليل منها وظيفة printk لتتبع المكان الذي تنتهي فيه الحزمة بالضبط. لقد وجدت بسرعة الحالة الصحيحة if، وحدقت فيه ببساطة لبعض الوقت، لأنه في ذلك الوقت اجتمع كل شيء أخيرًا في صورة كاملة: 231-1، رقم لا معنى له، مجال غير عامل... لقد كان جزءًا من التعليمات البرمجية في __udp_enqueue_schedule_skb:

if (rmem > (size + sk->sk_rcvbuf))
		goto uncharge_drop;

يرجى ملاحظة ما يلي:

  • rmem هو من النوع int
  • size هو من النوع u16 (عدد صحيح غير موقع بستة عشر بت) ويخزن حجم الحزمة
  • sk->sk_rcybuf هو من النوع int ويقوم بتخزين حجم المخزن المؤقت الذي، بحكم التعريف، يساوي القيمة الموجودة فيه net.core.rmem_default

عندما sk_rcvbuf يقترب من 231، قد يؤدي إلى جمع حجم الحزمة تجاوز عدد صحيح. وبما أنه int، فإن قيمته تصبح سالبة، وبالتالي يصبح الشرط صحيحًا عندما يكون خطأ (يمكنك قراءة المزيد عن هذا في صلة).

يمكن تصحيح الخطأ بطريقة تافهة: عن طريق الصب unsigned int. لقد قمت بتطبيق الإصلاح وأعدت تشغيل النظام وعمل DNS مرة أخرى.

طعم النصر

لقد قمت بإرسال النتائج التي توصلت إليها إلى العميل وأرسلتها LKML تصحيح النواة. يسعدني أن كل قطعة من اللغز تتناسب مع بعضها البعض، ويمكنني أن أشرح بالضبط سبب ملاحظة ما لاحظناه، والأهم من ذلك أننا تمكنا من إيجاد حل للمشكلة بفضل عملنا الجماعي!

ومن الجدير بالذكر أن هذه الحالة كانت نادرة، ولحسن الحظ نادرًا ما نتلقى مثل هذه الطلبات المعقدة من المستخدمين.

قصة عن حزم DNS المفقودة من الدعم الفني لـ Google Cloud


المصدر: www.habr.com

إضافة تعليق