الأنابيب المسمّاة عمليّاً ── IPC القياسيّ في Windows من التصميم إلى الأمان

· · Windows, IPC, تطوير Windows, C#, C++, الأمان, Win32 API

«أريد خدمة مقيمة وواجهة إعدادات تتبادلان الأوامر.» «أريد عزل العمل الذي يحتاج صلاحيّات مسؤول فقط في عمليّة منفصلة.» «أريد لأدوات على الجهاز نفسه أن تمرِّر البيانات بعضها لبعض.» ── عندما يصبح هذا النوع من الاتّصال بين العمليّات (IPC) ضروريّاً على Windows، المعيار الذي ينبغي النظر فيه أوّلاً هو الأنبوب المسمّى.

كان مقال اختيار وسيلة الاتّصال بين عمليّات Windows يضع الأنابيب المسمّاة بوصفها «المرشّح الأوّل لـIPC على الجهاز نفسه». هذا المقال هو المعالجة التفصيليّة. لماذا هي المرشّح الأوّل، وكيف تختار الأوضاع وأشكال الخادم، وما الذي يجب حمايته عندما تستخدمها خدمة ذات صلاحيّات ── موجَّه إلى المطوِّرين الذين يكتبون تطبيقات أعمال وخدمات على Windows، وينظِّم المادّة لهذه الأحكام التصميميّة من المصادر الأوّليّة.

1. الخلاصة أوّلاً

  • الأنبوب المسمّى قناة ثنائيّة الاتّجاه بين العمليّات، فضاء أسمائها بالشكل \\.\pipe\name. يمكن إنشاء عدّة نُسَخ تحت الاسم نفسه وقبول عدّة عملاء في آن واحد.1
  • سبب كونها المرشّح الأوّل لـIPC على الجهاز نفسه هو نموذج الأمان. يمكن التحكّم بمن يتّصل عبر ACL، ويمكن للخادم فحص حساب Windows للعميل واستعارته (انتحال الهويّة). لا يملك TCP على localhost أياً من الأمرين.2
  • إذا أردت معاملة «كتابة واحدة = رسالة واحدة»، فاستخدم وضع الرسالة؛ وإذا كان لديك تجزئة إلى إطارات خاصّة بك، فاستخدم وضع البايت. حتّى في وضع الرسالة يلزم معالجة القراءات المجزّأة عند قصر المخزن (ERROR_MORE_DATA).3
  • عالج عدّة عملاء بـ«عدّة نُسَخ + إدخال/إخراج overlapped» أو بـ«async/await في .NET». العيّنة الرسميّة تعرض شكلاً يعالج عدّة نُسَخ على مؤشّر ترابط واحد.4
  • الحدّ الأدنى للأمان أربع نقاط: رفض البعيد (PIPE_REJECT_REMOTE_CLIENTS)، وجعل الـACL صريحة، وكشف الاختطاف عبر FILE_FLAG_FIRST_PIPE_INSTANCE، وتقليل مستوى انتحال الهويّة في جانب العميل.56
  • بالنسبة لـImpersonateNamedPipeClient، التحقّق من قيمة الإرجاع هو خطّ الحياة. تجاهل الفشل فتستمرّ المعالجة بصلاحيّات الخادم.6

2. ما الأنبوب المسمّى ── فضاء الأسماء، والنُسَخ، وكيفيّة الاتّصال

الأنبوب المسمّى قناة تُعرَّف باسم مثل \\.\pipe\MyCompany.MyApp.Control. ينشئه الخادم بـCreateNamedPipe، ويفتح العميل الاسم نفسه بـCreateFile. بعد الفتح، يقرأ الجانبان ويكتبان بـReadFile / WriteFile ── النقطة المميِّزة أنّك تستخدمه بنفس شكل إدخال/إخراج الملفّات.1

المفهوم المهمّ هو النسخة (instance). يمكن إنشاء عدّة نُسَخ لأنبوب بالاسم نفسه، والنسخة الواحدة قناة واحدة مع عميل واحد. استدعاء CreateNamedPipe الأوّل يقرِّر العدد الأقصى للنُسَخ (أو بلا حدّ).3

لاتّصال جانب العميل وصفة قياسيّة. عندما تكون كلّ النُسَخ مشغولة، يفشل CreateFile بـERROR_PIPE_BUSY، فتنتظر نسخة فارغة بـWaitNamedPipe ثمّ تعيد المحاولة. كذلك، الوصول الذي تحدِّده عند الفتح يجب أن يطابق الاتّجاه الذي أنشأه الخادم ── يمكن فتح أنبوب ثنائيّ الاتّجاه بتحديد قراءة أو كتابة، لكن أنبوباً صادراً يكتب منه الخادم فقط يجب فتحه للقراءة فقط، وأنبوباً وارداً يقرأ منه الخادم فقط يجب فتحه للكتابة فقط، وإلا فشل CreateFile.7

اتّجاه الأنبوب ومواصفات وصول العميليمكن للعميل فتح أنبوب ثنائيّ الاتّجاه بتحديد قراءة أو كتابة، لكن يجب فتح أنبوب صادر يكتب منه الخادم فقط للقراءة فقط، وأنبوب وارد يقرأ منه الخادم فقط للكتابة فقطثنائيّ الاتّجاهصادرواردالاتّجاه الذي أنشأه الخادم؟القراءة أو الكتابة جائزةافتح للقراءة فقطافتح للكتابة فقط

الشكل 1: عدم تطابق الاتّجاه مع مواصفات الوصول يصبح فشل CreateFile. عند التحقيق في خطأ اتّصال، افحص هنا أوّلاً.

البنية الأساسيّة للأنبوب المسمّىينشئ الخادم عدّة نُسَخ أنبوب بالاسم نفسه وينتظر الاتّصال بـ ConnectNamedPipe؛ كلّ عميل يفتح الاسم بـ CreateFile وتكون له قناة ثنائيّة الاتّجاه واحداً لواحد مع نسخة واحدةالخادمالنسخة 1النسخة 2النسخة 3العميل أالعميل بالعميل ج

الشكل 2: باحتفاظ عدّة نُسَخ بالاسم نفسه، يمكن لخادم واحد التحدّث واحداً لواحد مع عدّة عملاء في الوقت نفسه.

يمكن أيضاً فتح الأنابيب المسمّاة عن بُعد عبر SMB (\\server\pipe\name)، لكن في تصميم حديث يكاد لا يوجد سبب لاستخدام ذلك بنشاط؛ المسألة بالأحرى عدم تركه مفتوحاً حين لا تستخدمه (الفصل 5).

3. وضع البايت ووضع الرسالة

للأنبوب وضعا نقل.3

  • وضع البايت (PIPE_TYPE_BYTE): «تدفّق بايتات متواصل» مثل TCP. تقرِّر بنفسك أين تنتهي الرسالة الواحدة (تصمِّم تجزئة إلى إطارات مثل بادئة طول).
  • وضع الرسالة (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): تُعامَل الكتابة الواحدة رسالةً واحدة، ويستقبلها القارئ بتلك الوحدة. هذا أسهل لتبادلات الطلب/الاستجابة.

لوضع الرسالة رفيق مريح أيضاً، TransactNamedPipe، الذي يرسل طلباً ويستقبل الاستجابة في استدعاء واحد.8 لكن ثمّة مطبّ. إذا كان مخزن الاستقبال أصغر من الرسالة كاملةً، تُرجع القراءة ERROR_MORE_DATA وتصبح قراءة مجزّأة. لا تفترض أنّ وضع الرسالة يعني «قراءة واحدة تجلب الكلّ دائماً»؛ ما زلت تحتاج إلى حلقة تقرأ الباقي. لاحظ أنّ وضع القراءة إعداد لكلّ مقبض، وCreateNamedPipe يقرِّره في جانب الخادم فقط. يحدِّده العميل بـSetNamedPipeHandleState بعد CreateFile (في .NET، ReadMode بعد الاتّصال).7

حلقة القراءة المجزّأة في وضع الرسالةإذا نجح ReadFile فالرسالة مكتملة؛ إذا أرجع ERROR_MORE_DATA تقرأ الباقي الذي لم يسع في المخزن وتُلحقه؛ أيّ خطأ آخر يُعامَل انقطاعاًنجاحERROR_MORE_DATAأيّ خطأ آخراقرأ بـ ReadFileالنتيجة؟الرسالة مكتملةاقرأ الباقي وألحقهعاملْه انقطاعاً

الشكل 3: حتّى في وضع الرسالة تحتاج إلى «حلقة قراءة الباقي»؛ بدونها تنكسر الرسائل الكبيرة فقط.

الفرق بين وضع البايت ووضع الرسالةفي وضع البايت تصبح ثلاث كتابات تدفّق بايتات متواصلاً وعلى المستقبِل تقسيمه؛ في وضع الرسالة تُحفَظ وحدة كلّ كتابة وتصل إلى المستقبِل كما هيوضع البايت: اكتب AAA ثمّ BB ثمّ CCCCيُستقبَل كتدفّق AAABBCCCCتصمِّم التجزئة بنفسكوضع الرسالة: الكتابات الثلاث نفسهاثلاث رسائل: AAA وBB وCCCCوحدات الكتابة محفوظة

الشكل 4: وضع الرسالة يحفظ «وحدة الكتابة» ويسلِّمها. يصبح تصميم التجزئة غير ضروريّ؛ فقط لا تنسَ معالجة القراءات المجزّأة.

قاعدة الاختيار العمليّة بسيطة. إذا كان التبادل على شكل «طلب واستجابة»، فوضع الرسالة. إذا كنت تحمل شكلاً يملك تجزئة مدمَجة مسبقاً (بيانات مُسلسَلة مسبوقة بالطول أو نقل تدفّق)، فاستخدم وضع البايت. في .NET، تحديد PipeTransmissionMode.Message يقابل الأوّل.9

4. تصميم الخادم ── مؤشّر ترابط لكلّ عميل، أم Overlapped؟

عمليّة الخادم الأساسيّة هي الحلقة «أنشئ نسخة → انتظر عميلاً بـConnectNamedPipe → اقرأ واكتب → اقطع وانتقل إلى العميل التالي». هناك شكلان للتحدّث إلى عدّة عملاء في آن واحد.

متزامن، مؤشّر ترابط لكلّ نسخة. تخصِّص مؤشّر ترابط لكلّ نسخة، وكلّ منها يتحدّث إلى عميله بإدخال/إخراج متزامن. الشيفرة مباشرة، لكنّك تستهلك مؤشّر ترابط لكلّ عميل، وتحتاج أيضاً إلى طريقة للخروج من الإدخال/الإخراج الحاجب عند إيقاف الكلّ.

Overlapped (غير متزامن). تنشئ النُسَخ بـFILE_FLAG_OVERLAPPED، وتُصدر ConnectNamedPipe / ReadFile / WriteFile بشكل غير متزامن، ويجعل عدد صغير من مؤشّرات الترابط يعالج الاكتمال لكلّ النُسَخ. عيّنة Microsoft الرسميّة تعرض خادماً ينتظر على مصفوفة أحداث بـWaitForMultipleObjects ويعالج عدّة نُسَخ على مؤشّر ترابط واحد.4 الحساب العامّ للإدخال/الإخراج غير المتزامن كما شُرح في مقال سلسلة الإدخال/الإخراج، وعلى نطاق أكبر يمكن أيضاً ربط IOCP أو إدخال/إخراج مجمع مؤشّرات الترابط.

بنية خادم overlappedتُستقبَل اكتمالات العمليّات غير المتزامنة لكلّ نسخة على مصفوفة أحداث، وينتظر عدد صغير من مؤشّرات الترابط بـ WaitForMultipleObjects ويُقدِّم النسخة المكتملة، فيُفصَل عدد مؤشّرات الترابط عن عدد العملاءعمليّة غير متزامنة للنسخة 1مصفوفة الأحداثعمليّة غير متزامنة للنسخة 2عمليّة غير متزامنة للنسخة 3انتظر الاكتمال بـ WaitForMultipleObjectsقدِّم النسخة المكتملة

الشكل 5: الشكل overlapped يفصل عدد مؤشّرات الترابط عن عدد العملاء. العيّنة الرسميّة تدير هذه الدورة على مؤشّر ترابط واحد.

.NET يكاد يلغي هذا الاختيار. باستخدام WaitForConnectionAsync / ReadAsync / WriteAsync الخاصّة بـNamedPipeServerStream مع async/await، تحصل على كفاءة overlapped بشيفرة مباشرة كالشكل المتزامن.9

// C#: skeleton of a server that accepts multiple clients
while (!token.IsCancellationRequested)
{
    var server = new NamedPipeServerStream(
        "MyCompany.MyApp.Control",
        PipeDirection.InOut,
        NamedPipeServerStream.MaxAllowedServerInstances,
        PipeTransmissionMode.Message,
        PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);

    try
    {
        await server.WaitForConnectionAsync(token);
    }
    catch
    {
        await server.DisposeAsync();        // dispose yourself when leaving before a connection
        throw;
    }
    _ = HandleClientAsync(server, token);   // ownership after connect goes to the handler
}

PipeOptions.CurrentUserOnly مواصفة «تسمح بالاتّصال فقط من عمليّات المستخدم نفسه»، افتراضيّ مريح وآمن يغنيك عن كتابة ACL بنفسك.10 لا يمكن استخدامه في إعداد يعبر المستخدمين (خدمة ↔ تطبيق في جلسة مستخدم، وما شابه)، ففي تلك الحالة تنتقل إلى تصميم الـACL في الفصل التالي.

حلقة القبول لخادم .NET غير متزامنتنشئ حلقة القبول NamedPipeServerStream، وتنتظر اتّصالاً بـ WaitForConnectionAsync، وعند الوصول تفصل معالجة العميل بشكل غير متزامن وتعود فوراً إلى القبول التالي، فتُعالَج الاتّصالات المتزامنة بشيفرة مباشرةأنشئ تدفّق الخادمانتظر بـ WaitForConnectionAsyncوصل اتّصالافصل معالجة العميل بشكل غير متزامن

الشكل 6: تلتزم حلقة القبول بدورة «انتظر → افصل → التالي»، وتتقدّم معالجة كلّ عميل على التوازي.

5. الأمان ── أربع واجبات عندما تستخدم خدمة ذات صلاحيّات أنابيب

أكبر سبب لكون الأنابيب المسمّاة المرشّح الأوّل لـIPC على الجهاز نفسه هو نموذج الأمان، لكن ذلك فقط إذا ضبطته بشكل صحيح. خصوصاً في تصميم الوسيط «خدمة بصلاحيّات مسؤول + تطبيق واجهة بصلاحيّات منخفضة»، الأنبوب نفسه هو حدّ الصلاحيّات. هناك أربع نقاط يجب تثبيتها.

(1) ارفض البعيد. أنبوب قصدتَه IPC محلّيّاً وهو قابل للفتح من الشبكة هو، بذاته، سطح هجوم. حدِّد PIPE_REJECT_REMOTE_CLIENTS على CreateNamedPipe فتُرفَض اتّصالات العملاء البعيدين تلقائيّاً.5

(2) اجعل الـACL صريحة. مرِّر واصف أمان في SECURITY_ATTRIBUTES وضيِّق المستخدمين والمجموعات المسموح لها بالاتّصال. لا تعطِ العميل GENERIC_WRITE ── حقّ FILE_CREATE_PIPE_INSTANCE المضمَّن فيه يتيح لعميل مخوَّل نفسه إنشاء نسخة خادم بالاسم نفسه وسرقة الاتّصالات اللاحقة. امنح القراءة والكتابة حقوقاً فرديّة، ولا تمرِّر حقّ إنشاء النسخة.11

(3) امنع اختطاف الاسم. أسماء الأنابيب لمن سبق. إذا أنشأت عمليّة خبيثة أنبوباً بالاسم نفسه أوّلاً وانتظرت، يتّصل العملاء بالخادم المزيف. يحدِّد الخادم FILE_FLAG_FIRST_PIPE_INSTANCE عند إنشاء النسخة الأولى، ضامناً «أنا الأوّل»، وإن فشل ذلك يشتبه باختطاف ويتوقّف. هذا العلم للنسخة الأولى التي تطالب بالاسم فقط؛ وضعه على النسخة الثانية وما بعدها يجعل الإنشاء يفشل.3

(4) يقلِّل العميل مستوى انتحال الهويّة إلى ما يلزم. هذا استعداد لحالة كون الطرف خادماً مزيفاً. إذا حدَّد العميل **SECURITY_SQOS_PRESENT SECURITY_IDENTIFICATION** على CreateFile، يمكن للخادم تعريف العميل لكن لا يمكنه استعارة تلك الصلاحيّات والعمل بها.2 هذا مقايضة مع سير عمل انتحال الهويّة ── في تصميم وسيط ينفِّذ الخادم فيه وصولاً حقيقيّاً بصلاحيّات العميل، مستوى التعريف لا يكفي لنجاح الانتحال، وتحتاج إلى السماح بـSECURITY_IMPERSONATION. ذلك السماح مشروط بـالتأكّد من الاتّصال بالخادم الحقيقيّ. مكافحة الاختطاف في جانب الخادم آليّة تلاحظ عبر فشل البدء فقط؛ إذا غابت الخدمة الحقيقيّة وأنشأ مهاجم الأنبوب بالاسم نفسه أوّلاً، يمكن للعميل الاتّصال بالخادم المزيف. اسمح به فقط عندما تستطيع تأكيد الطرف عبر بدء خدمة مضمون أو مصادقة متبادلة بعد الاتّصال.

فحص الهويّة واستعارة الصلاحيّات في جانب الخادم هو ImpersonateNamedPipeClient. استدعِ هذا بعد قراءة طلب من الأنبوب فيبدأ مؤشّر الترابط المستدعي العمل في سياق أمان مرسِل آخر رسالة قُرئت. افتح ملفّاً بصلاحيّات العميل فيُجرى فحص الوصول مقابل العميل ── الآليّة التي تنفِّذ بها خدمة ذات صلاحيّات «العمليّة المطلوبة، بصلاحيّات الطالب».6 الشرط المطلق لاستخدامه هو التحقّق من قيمة الإرجاع. واصل بعد فشل انتحال الهويّة فتعمل العمليّات اللاحقة بصلاحيّات الخادم المرتفعة نفسها. تنصّ الوثائق الرسميّة صراحةً على أنّه «عند الفشل يجب عدم تنفيذ طلب العميل». مع RevertToSelf بعد العمل، تنطبق ممارسات مقال رموز انتحال الهويّة كما هي.

تدفّق معالجة طلب يستخدم انتحال الهويّةيقرأ الخادم طلباً من الأنبوب، ويتأكّد من نجاح ImpersonateNamedPipeClient، ثمّ ينفِّذ العمليّة بصلاحيّات العميل ويعود إلى سياقه بـ RevertToSelf. إن فشل انتحال الهويّة يرفض الطلب دون تنفيذهالخادمالعميلالخادمالعميلعند الفشل، ارفض دون تنفيذ الطلبأرسل طلباًاقرأ الطلبImpersonateNamedPipeClientنفِّذ العمليّة بصلاحيّات العميلRevertToSelf لاستعادة السياق الأصليّأجب بالنتيجة

الشكل 7: تأكيد نجاح انتحال الهويّة وRevertToSelf الموثوق يأتيان معاً. واصل عند الفشل فيعمل بصلاحيّات الخادم.

أربع نقاط تحمي أنبوب خدمة ذات صلاحيّاتجانب الخادم يحصِّن المدخل برفض البعيد وACL صريحة وضمان النسخة الأولى؛ جانب العميل يحدِّد أدنى مستوى انتحال هويّة لازم حتّى لا يستعير خادم مزيف الصلاحيّات(ضيِّقه إلى مستوى التعريف إن كان التصميم لا يتيح للخادم استعارة الصلاحيّات)جانب العميلحدِّد أدنى مستوى انتحالجانب الخادمPIPE_REJECT_REMOTE_CLIENTSقيِّد المتّصلين بـ ACLFIRST_PIPE_INSTANCE(الأولى فقط)الأنبوب حدّ صلاحيّات

الشكل 8: في تصميم يكون فيه الأنبوب حدّ الصلاحيّات، نفِّذ النقاط الثلاث في جانب الخادم زائد النقطة الواحدة في جانب العميل كمجموعة.

6. مطبّات عمليّة

سباق ترتيب البدء. إذا جاء عميل للاتّصال قبل أن ينشئ الخادم الأنبوب، تحصل على خطأ «الأنبوب غير موجود». يبني جانب العميل «غير موجود → انتظر قليلاً وأعد المحاولة». بالمقابل، المبدأ في جانب الخادم هو بدء الاستماع بـConnectNamedPipe قبل بدء العميل.8

تدفّق إعادة محاولة اتّصال العميلافتح الأنبوب بـ CreateFile؛ إن لم يكن موجوداً فانتظر قليلاً وأعد المحاولة؛ إن كانت كلّ النُسَخ مشغولة(ERROR_PIPE_BUSY) فانتظر نسخة فارغة بـ WaitNamedPipe ثمّ أعد المحاولة؛ عند النجاح ادخل الاتّصالنجاحالأنبوب غير موجودERROR_PIPE_BUSYافتح بـ CreateFileالنتيجة؟ابدأ الاتّصالانتظر قليلاً(الخادم لم يبدأ)انتظر نسخة فارغة بـ WaitNamedPipe

الشكل 9: معالجة اتّصال العميل تميِّز نوعَي الفشل «غير موجود» و«ممتلئ»، وتعيد كليهما إلى إعادة المحاولة.

كشف الانقطاع. عندما يخرج الطرف الآخر، تفشل القراءة/الكتابة بـERROR_BROKEN_PIPE وما شابه. ذلك ليس شذوذاً؛ إنّه يوميّات الاتّصال. يكشف الخادم الانقطاع، ويستدعي DisconnectNamedPipe للنسخة، ويستعدّ للاتّصال التالي؛ يعيد العميل الاتّصال ── فكرة «إعادة الاتّصال المتكافئة» الموصوفة في مقال النوم/الاستئناف تنطبق هنا أيضاً.

افتراضات حجم الرسالة. فوق القراءات المجزّأة في وضع الرسالة (الفصل 3)، إن لم تقرِّر ضمن البروتوكول «ما الحدّ الأقصى لبايتات الرسالة الواحدة»، يمكن لطرف خبيث (أو فيه عيب) إهدار ذاكرتك برسالة ضخمة. قرِّر سقفاً، واقطع إن تُجُوِّز؛ هذا هو النهج الآمن.

اكتمال الكتابة واستقبال الطرف أمران مختلفان. نجاح WriteFile لا يعني أنّ تطبيق الطرف قد عالج البيانات. العمليّات التي تحتاج يقيناً تُسنَد بتصاميم مثل التأكيد برسالة استجابة، وتضمين تطابق الطلب والاستجابة في البروتوكول.

7. الخلاصة

  • الأنابيب المسمّاة هي المرشّح الأوّل لـIPC على الجهاز نفسه. الأسباب هي سهولة الاستخدام نفسها كإدخال/إخراج الملفّات، والتكامل مع نموذج أمان Windows من ACL وانتحال الهويّة.
  • اختيار الوضع هو «وضع الرسالة للطلب والاستجابة، ووضع البايت إن كان لديك تجزئة خاصّة بك». حتّى في وضع الرسالة يلزم معالجة القراءات المجزّأة (ERROR_MORE_DATA).
  • العملاء المتعدّدون هم عدّة نُسَخ + overlapped، أو async/await في .NET. للعمل الجديد الشكل غير المتزامن في .NET هو المباشر.
  • على أنبوب هو حدّ صلاحيّات، خذ رفض البعيد، وACL صريحة، وFIRST_PIPE_INSTANCE (النسخة الأولى فقط)، وتقليل مستوى انتحال الهويّة في جانب العميل كمجموعة.
  • بالنسبة لـImpersonateNamedPipeClient، التحقّق من قيمة الإرجاع وRevertToSelf هما خطّ الحياة.
  • انسج «يوميّات الاتّصال» ── ترتيب البدء، والانقطاع، وسقف الرسالة، وتأكيد الاستجابة ── في تصميم البروتوكول.

الأنابيب المسمّاة واجهة برمجيّة قديمة، لكن لاستخدام «جعل العمليّات تتحدّث على الجهاز نفسه مع احترام حدود حسابات Windows»، ما زالت الأداة الأكثر طبيعيّة للمهمّة. نقاط الحكم التصميميّ تكاد تُستنفَد ضمن نطاق هذا المقال. بعد ذلك، اكتب بروتوكولك على ورقة واحدة قبل أن تبدأ التنفيذ.

مقالات ذات صلة

مجالات الاستشارة ذات الصلة

تتعامل شركة كومورا سوفت ذ.م.م. مع التصميم والتنفيذ الذي يتضمّن الاتّصال بين العمليّات ── فصل خدمة عن تطبيق واجهة، وعزل صلاحيّات المسؤول، وما شابه ── واستبدال IPC قائم (ذاكرة مشتركة، مقبس منزليّ، COM، وغيرها) بأنابيب مسمّاة، ومراجعات أمان اتّصال أنبوب خدمة ذات صلاحيّات. الاستشارة بدءاً من مناقشة تصميم البروتوكول مرحَّب بها.

روابط مرجعيّة

  1. Microsoft Learn, Named Pipes. حول كون الأنبوب المسمّى قناة أحاديّة أو ثنائيّة الاتّجاه بين خادم أنبوب وعميل أنبوب واحد أو أكثر؛ وحول مشاركة كلّ نسخة الاسم نفسه مع امتلاك مخازن ومقابض مستقلّة؛ وحول إمكان الاستخدام من عمليّات محلّيّة وبعيدة.  2

  2. Microsoft Learn, Impersonating a Named Pipe Client. حول إتاحة انتحال الهويّة لمؤشّر ترابط الخادم العمل ضمن صلاحيّات العميل؛ وحول كون مستوى الانتحال الافتراضيّ SecurityImpersonation؛ وحول إمكان تحكّم العميل بمستوى الانتحال بعلم SECURITY_SQOS_PRESENT عند CreateFile (SECURITY_IDENTIFICATION يسمح بالتعريف فقط).  2

  3. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). حول اتّجاه الأنبوب (وارد، صادر، ثنائيّ الاتّجاه)، ونوع البايت ونوع الرسالة (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) ووضع القراءة (PIPE_READMODE_MESSAGE)، والعدد الأقصى للنُسَخ (PIPE_UNLIMITED_INSTANCES)، والوضع غير المتزامن عبر FILE_FLAG_OVERLAPPED، وضمان النسخة الأولى عبر FILE_FLAG_FIRST_PIPE_INSTANCE، والمهلة الافتراضيّة لـWaitNamedPipe.  2 3 4

  4. Microsoft Learn, Named Pipe Server Using Overlapped I/O. حول العيّنة الرسميّة لخادم بمؤشّر ترابط واحد يعالج اتّصالات متزامنة مع عدّة عملاء عبر عمليّات overlapped. حول الشكل الذي ينتظر بنية OVERLAPPED والحدث لكلّ نسخة بـWaitForMultipleObjects ويُقدِّم آلة حالات النسخة المكتملة، وحول تأكيد اكتمال الإدخال/الإخراج معلَّق بـGetOverlappedResult.  2

  5. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). حول وضعَي العميل البعيد، PIPE_ACCEPT_REMOTE_CLIENTS (قبول الاتّصالات البعيدة وفحصها مقابل واصف الأمان) وPIPE_REJECT_REMOTE_CLIENTS (رفض اتّصالات العملاء البعيدين تلقائيّاً).  2

  6. Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). حول بدء مؤشّر ترابط في جانب الخادم انتحال الهويّة في سياق أمان عميل آخر رسالة قُرئت من الأنبوب؛ وحول العودة بـRevertToSelf بعد الاكتمال؛ وحول أنّ المواصلة بعد فشل انتحال الهويّة تتسبّب في التنفيذ في سياق عمليّة الخادم نفسه (ذي الصلاحيّات)، لذا يجب التحقّق دائماً من قيمة الإرجاع وعند الفشل عدم تنفيذ طلب العميل.  2 3

  7. Microsoft Learn, Named Pipe Client. حول فتح العميل الأنبوب بـCreateFile؛ وحول ERROR_PIPE_BUSY عندما تكون كلّ النُسَخ مشغولة، وانتظار نسخة فارغة بـWaitNamedPipe؛ وحول كون المقبض المفتوح افتراضيّاً قراءة بايت وحاجباً وغير overlapped، مع إمكان تغييره إلى وضع قراءة الرسالة عبر SetNamedPipeHandleState.  2

  8. Microsoft Learn, Named Pipe Operations. حول العمليّات overlapped عبر ReadFileEx / WriteFileEx، وقراءة غير مستهلِكة عبر PeekNamedPipe، وقيام TransactNamedPipe بإرسال الطلب واستقبال الاستجابة في استدعاء واحد على أنبوب ثنائيّ الاتّجاه من نوع الرسالة، وإمكان تسبّب قراءة حاجبة قبل بدء العميل في سباق.  2

  9. Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). حول الاتّصال والقراءة/الكتابة بـNamedPipeServerStream / NamedPipeClientStream، والنقل بوحدة الرسالة عبر PipeTransmissionMode.Message، ومعالجة عدّة عملاء بطرائق غير متزامنة.  2

  10. Microsoft Learn, PipeOptions Enum (System.IO.Pipes). حول تفعيل الإدخال/الإخراج غير المتزامن بـAsynchronous، وحول إمكان سماح CurrentUserOnly بالاتّصال فقط مع عمليّات المستخدم نفسه (ونفس مستوى الرفع). 

  11. Microsoft Learn, Named Pipe Security and Access Rights. حول تركيب حقوق وصول الأنبوب المسمّى؛ وحول تضمين GENERIC_WRITE لـFILE_CREATE_PIPE_INSTANCE، بحيث إن منح العميل كتابة عامّة يسمح أيضاً بإنشاء نسخة خادم؛ وحول منح قراءة البيانات وكتابتها حقوق وصول فرديّة. 

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

كيف أختار بين الأنبوب المسمّى وTCP (مقبس localhost)؟
للاتّصال بين العمليّات على الجهاز نفسه، الأنبوب المسمّى هو المرشّح الأوّل. السبب هو نموذج الأمان. يمكن للأنبوب التحكّم بـ«من يجوز له الاتّصال» على مستوى نظام التشغيل عبر واصف أمان Windows (ACL)، ويمكن للخادم فحص حساب Windows للطرف المتّصل واستعارته عبر ImpersonateNamedPipeClient. هذا بخلاف منفذ TCP على localhost، الذي يمكن لأيّ كان الاتّصال به، فتحتاج إلى إثبات هويّة الطرف بمصادقة خاصّة بك. في المقابل، الخيارات القائمة على TCP أفضل عندما يُرجَّح تطوّر الاتّصال عن بُعد لاحقاً، أو عند الحديث أيضاً مع عمليّات على أنظمة تشغيل أخرى، أو عند الرغبة في إعادة استخدام أصل بروتوكول قائم مثل gRPC. هذا الحكم موضَّح أيضاً في مقال اختيار وسيلة الاتّصال بين عمليّات Windows.
هل أستخدم وضع البايت أم وضع الرسالة؟
إذا أردت معاملة «كتابة واحدة = وحدة معنى واحدة»، فوضع الرسالة (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) مريح. يمكن للمستقبِل القراءة بالوحدات التي كتبها المرسِل، فلا تحتاج إلى إدارة الحدود بنفسك. وضع البايت «تدفّق بايتات متواصل» مثل TCP، وعليك تصميم التجزئة إلى إطارات بنفسك ── بادئة طول مثلاً. إذا كنت تحمل بروتوكولاً يملك تجزئة مسبقاً (شكل مُسلسَل مسبوق بالطول مثلاً)، فوضع البايت مناسب. نقطة حذر: حتّى في وضع الرسالة، إذا كان مخزن الاستقبال أصغر من الرسالة تحصل على قراءة مجزّأة (ERROR_MORE_DATA)، لذا يلزم معالجتها. كذلك، وضع القراءة إعداد لكلّ مقبض، وCreateNamedPipe يضبطه في جانب الخادم فقط. يجب على العميل تحديد PIPE_READMODE_MESSAGE عبر SetNamedPipeHandleState بعد CreateFile. في .NET يحدِّد الخادم PipeTransmissionMode.Message، ويضبط العميل NamedPipeClientStream.ReadMode على Message بعد الاتّصال.
كيف أبني خادماً يتحدّث إلى عدّة عملاء في آن واحد؟
يمكن للأنبوب المسمّى إنشاء عدّة نُسَخ تحت الاسم نفسه، وكلّ نسخة تتعامل مع عميل واحد. هناك شكلان. الأوّل تصميم متزامن يخصِّص مؤشّر ترابط لكلّ عميل؛ التنفيذ مباشر، لكنّه يستهلك مؤشّر ترابط لكلّ عميل. الثاني استخدام إدخال/إخراج غير متزامن مع FILE_FLAG_OVERLAPPED وجعل عدد صغير من مؤشّرات الترابط يعالج ConnectNamedPipe وReadFile وWriteFile لكلّ النُسَخ؛ عيّنة Microsoft الرسميّة تعرض أيضاً تنفيذاً يعالج عدّة نُسَخ على مؤشّر ترابط واحد. في .NET يمكن كتابة الشكل غير المتزامن بنفس المباشرة تقريباً التي للشكل المتزامن، عبر NamedPipeServerStream.WaitForConnectionAsync وasync/await. ما لم يكن لديك سبب خاصّ، الشكل غير المتزامن في .NET هو ما أوصي به للتنفيذات الجديدة.
ما الحدّ الأدنى الذي ينبغي فعله لأمان الأنبوب المسمّى؟
أربع نقاط. أولاً، إن لم تُحتَج الاتّصالات عن بُعد، حدِّد PIPE_REJECT_REMOTE_CLIENTS وارفض صراحةً الاتّصالات عبر الشبكة. ثانياً، اضبط ACL مناسبة عبر SECURITY_ATTRIBUTES وضيِّق المستخدمين والمجموعات المسموح لها بالاتّصال (الـACL الافتراضيّ فضفاض لبعض الاستخدامات). ثالثاً، حدِّد FILE_FLAG_FIRST_PIPE_INSTANCE عند إنشاء النسخة الأولى، لتكشف «اختطاف الاسم» الذي يُنشَأ فيه أنبوب بالاسم نفسه أوّلاً (لا تضع هذا العلم على النسخة الثانية وما بعدها). رابعاً، العميل الذي يريد فقط أن يعرِّفه الخادم يجب أن يحدِّد SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION على CreateFile، حتّى لا يستطيع خادم مزيف استعارة (انتحال) صلاحيّاته. في تصميم وسيط ينفِّذ الخادم فيه وصولاً حقيقيّاً بصلاحيّات العميل، يجب السماح بانتحال الهويّة، لذا تُطبَّق هذه القيود أو لا تُطبَّق بحسب ما إذا كان التصميم يتيح للخادم استعارة الصلاحيّات.
هل ثمّة محاذير عند استخدام ImpersonateNamedPipeClient؟
الأهمّ هو التحقّق من قيمة الإرجاع. إن واصلت بعد فشل انتحال الهويّة، تعمل العمليّات اللاحقة بصلاحيّات عمليّة الخادم نفسها (غالباً مرتفعة)، وتمرّ عمليّات لم يكن ينبغي السماح بها للعميل. الوثائق الرسميّة تنصّ صراحةً أيضاً على أنّه عند الفشل يجب عدم تنفيذ طلب العميل. يلزم أيضاً استدعاؤه فقط بعد قراءة شيء ── يُنفَّذ انتحال الهويّة في سياق «آخر رسالة قُرئت من الأنبوب» ── والعودة بموثوقيّة إلى السياق الأصليّ عبر RevertToSelf عند انتهاء العمل. الآليّات حول انتحال الهويّة (الرموز، مستويات الانتحال، SeImpersonatePrivilege) مشروحة بالتفصيل في مقال رموز انتحال الهويّة.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

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

العودة إلى المدونة