“אני רוצה ששירות תושב וממשק הגדרות יחליפו פקודות.” “אני רוצה לבודד רק את העבודה שדורשת הרשאות מנהל לתהליך נפרד.” “אני רוצה שכלים באותו מחשב יעבירו נתונים זה לזה.” — כשנדרשת תקשורת בין-תהליכית (IPC) כזו ב-Windows, הסטנדרט שכדאי לשקול ראשון הוא Named Pipe.
המאמר על בחירת תקשורת בין-תהליכית ב-Windows מיקם את Named Pipes כ”המועמד הראשון ל-IPC באותו מחשב”. המאמר הזה הוא הטיפול המפורט. למה הם המועמד הראשון, איך בוחרים מצבים וצורות שרת, ומה חייבים להגן כששירות בעל הרשאות משתמש בהם — מיועד למפתחות ולמפתחים שכותבים יישומים עסקיים ושירותים ב-Windows, והוא מסדר את חומר השיפוט התכנוני ממקורות ראשוניים.
1. השורה התחתונה קודם
- Named Pipe הוא ערוץ דו-כיווני בין תהליכים עם מרחב שמות מהצורה
\\.\pipe\name. אפשר ליצור כמה מופעים תחת אותו שם ולקבל כמה לקוחות במקביל.1 - הסיבה שהם המועמד הראשון ל-IPC באותו מחשב היא מודל האבטחה. אפשר לשלוט במי שמתחבר עם ACL, והשרת יכול לבדוק ולשאול (להתחזות ל)חשבון ה-Windows של הלקוח. ל-TCP ב-localhost אין אף אחד מאלה.2
- אם רוצים לטפל ב”כתיבה אחת = הודעה אחת”, השתמשו במצב הודעות; אם כבר יש לכם framing משלכם, השתמשו במצב בתים. גם במצב הודעות עדיין צריך לטפל בקריאות מפוצלות על מאגר קצר (ERROR_MORE_DATA).3
- טפלו בכמה לקוחות עם “כמה מופעים + I/O overlapped” או עם “async/await ב-.NET”. הדוגמה הרשמית מציגה צורה שמעבדת כמה מופעים על תהליכון יחיד.4
- מינימום האבטחה הוא ארבע נקודות: דחיית מרוחק (PIPE_REJECT_REMOTE_CLIENTS), ACL מפורש, זיהוי חטיפה עם FILE_FLAG_FIRST_PIPE_INSTANCE, וצמצום רמת ההתחזות בצד הלקוח.56
- ב-
ImpersonateNamedPipeClient, בדיקת ערך ההחזרה היא קו החיים. מתעלמים מכישלון — והעיבוד ממשיך בהרשאות השרת.6
2. מהו Named Pipe — מרחב שמות, מופעים ואיך חיבורים עובדים
Named Pipe הוא ערוץ שמזוהה בשם כמו \\.\pipe\MyCompany.MyApp.Control. השרת יוצר אותו עם CreateNamedPipe, והלקוח פותח את אותו שם עם CreateFile. ברגע שהוא פתוח, שני הצדדים קוראים וכותבים עם ReadFile / WriteFile — הנקודה הייחודית היא שאפשר להשתמש בו באותה צורה כמו I/O של קבצים.1
המושג החשוב הוא המופע. אפשר ליצור כמה מופעים של צינור באותו שם, ומופע אחד הוא ערוץ אחד עם לקוח אחד. הקריאה הראשונה ל-CreateNamedPipe קובעת את מספר המופעים המרבי (או ללא הגבלה).3
לחיבור בצד הלקוח יש מתכון סטנדרטי. כשכל המופעים תפוסים, CreateFile נכשל עם ERROR_PIPE_BUSY, אז ממתינים למופע פנוי עם WaitNamedPipe ואז מנסים שוב. בנוסף, הגישה שמציינים בפתיחה צריכה להתאים לכיוון שהשרת יצר — צינור דו-כיווני אפשר לפתוח עם קריאה או כתיבה, אבל צינור יוצא שהשרת רק כותב אליו חייב להיפתח לקריאה בלבד, וצינור נכנס שהשרת רק קורא ממנו חייב להיפתח לכתיבה בלבד, אחרת CreateFile נכשל.7
flowchart TB
accTitle: כיוון הצינור ומפרט הגישה של הלקוח
accDescr: לקוח יכול לפתוח צינור דו-כיווני עם קריאה או כתיבה, אבל חייב לפתוח צינור יוצא שהשרת רק כותב אליו לקריאה בלבד, וצינור נכנס שהשרת רק קורא ממנו לכתיבה בלבד
q{"הכיוון שהשרת יצר?"} -->|"דו-כיווני"| dc["קריאה או כתיבה בסדר"]
q -->|"יוצא"| oc["פתיחה לקריאה בלבד"]
q -->|"נכנס"| ic["פתיחה לכתיבה בלבד"]
איור 1: אי-התאמה בין כיוון למפרט גישה הופכת לכישלון CreateFile. בחקר שגיאת חיבור, בדקו כאן קודם.
flowchart TB
accTitle: המבנה הבסיסי של Named Pipe
accDescr: השרת יוצר כמה מופעי צינור באותו שם וממתין לחיבור עם ConnectNamedPipe; כל לקוח פותח את השם עם CreateFile ומקבל ערוץ דו-כיווני אחד-לאחד עם מופע אחד
s["שרת"] --> i1["מופע 1"]
s --> i2["מופע 2"]
s --> i3["מופע 3"]
c1["לקוח A"] <--> i1
c2["לקוח B"] <--> i2
c3["לקוח C"] <--> i3
איור 2: בהחזקת כמה מופעים באותו שם, שרת אחד יכול לדבר אחד-לאחד עם כמה לקוחות באותו זמן.
אפשר לפתוח Named Pipes גם מרחוק דרך SMB (\\server\pipe\name), אבל בתכנון מודרני כמעט אין סיבה להשתמש בזה באופן פעיל; הבעיה היא דווקא לא להשאיר את זה פתוח כשלא משתמשים בזה (פרק 5).
3. מצב בתים ומצב הודעות
לצינור יש שני מצבי העברה.3
- מצב בתים (PIPE_TYPE_BYTE): “זרם בתים רציף” כמו TCP. אתם מחליטים בעצמכם איפה הודעה אחת נגמרת (מתכננים framing כמו קידומת אורך).
- מצב הודעות (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): כתיבה אחת מטופלת כהודעה אחת, והקורא מקבל אותה ביחידה הזו. זה קל יותר לחילופי בקשה/תשובה.
למצב הודעות יש גם בן-זוג נוח, TransactNamedPipe, ששולח בקשה ומקבל את התשובה בקריאה אחת.8 אבל יש מלכוד. אם מאגר הקבלה קטן מההודעה כולה, הקריאה מחזירה ERROR_MORE_DATA והופכת לקריאה מפוצלת. אל תניחו שמצב הודעות אומר “Read אחד תמיד מביא את הכול”; עדיין צריך לכתוב לולאה שקוראת את היתרה. שימו לב שמצב הקריאה הוא הגדרה לפי-handle, ו-CreateNamedPipe קובע אותו רק בצד השרת. הלקוח מציין אותו עם SetNamedPipeHandleState אחרי CreateFile (ב-.NET, ReadMode אחרי החיבור).7
flowchart TB
accTitle: לולאת קריאה מפוצלת במצב הודעות
accDescr: אם ReadFile מצליח ההודעה שלמה; אם הוא מחזיר ERROR_MORE_DATA קוראים את היתרה שלא נכנסה למאגר ומשרשרים אותה; כל שגיאה אחרת מטופלת כניתוק
read["קריאה עם ReadFile"] --> r{"התוצאה?"}
r -->|"הצלחה"| done["ההודעה שלמה"]
r -->|"ERROR_MORE_DATA"| more["קריאת היתרה ושרשור"]
more --> read
r -->|"כל שגיאה אחרת"| dis["טיפול כניתוק"]
איור 3: גם במצב הודעות צריך “לולאת קריאת-היתר”; בלי זה, רק הודעות גדולות נשברות.
flowchart TB
accTitle: ההבדל בין מצב בתים למצב הודעות
accDescr: במצב בתים שלוש כתיבות הופכות לזרם בתים רציף והמקבל צריך לפצל אותו; במצב הודעות יחידת כל כתיבה נשמרת ומגיעה למקבל כמות שהיא
bw["מצב בתים: כתיבת AAA, BB, CCCC"] --> br["התקבל כזרם AAABBCCCC"]
br --> bf["אתם מתכננים את ה-framing"]
mw["מצב הודעות: אותן שלוש כתיבות"] --> mr["התקבל כשלוש הודעות: AAA, BB, CCCC"]
mr --> mf["יחידות הכתיבה נשמרות"]
איור 4: מצב הודעות שומר את “יחידת הכתיבה” ומוסר אותה. תכנון framing מיותר; רק אל תשכחו לטפל בקריאות מפוצלות.
כלל הבחירה המעשי פשוט. אם החילופים הם בצורה של “בקשה ותשובה”, מצב הודעות. אם מעבירים צורה שכבר יש בה framing מובנה (נתונים מסורסרים עם קידומת אורך או העברת זרם), השתמשו במצב בתים. ב-.NET, ציון PipeTransmissionMode.Message מתאים לראשון.9
4. תכנון השרת — תהליכון אחד לכל לקוח, או Overlapped?
הפעולה הבסיסית של השרת היא הלולאה “צור מופע → המתן ללקוח עם ConnectNamedPipe → קרא וכתוב → נתק ועבור ללקוח הבא”. יש שתי צורות לדיבור עם כמה לקוחות במקביל.
סינכרוני, תהליכון אחד לכל מופע. מקצים תהליכון אחד לכל מופע, וכל אחד מדבר עם הלקוח שלו ב-I/O סינכרוני. הקוד פשוט, אבל צורכים תהליכון אחד לכל לקוח, וצריך גם דרך לצאת מ-I/O חוסם כשמכבים את הכול.
Overlapped (אסינכרוני). יוצרים מופעים עם FILE_FLAG_OVERLAPPED, מנפיקים ConnectNamedPipe / ReadFile / WriteFile באופן אסינכרוני, ומספר קטן של תהליכונים מטפל בהשלמה של כל המופעים. הדוגמה הרשמית של Microsoft מציגה שרת שממתין על מערך אירועים עם WaitForMultipleObjects ומעבד כמה מופעים על תהליכון יחיד.4 התיאור הכללי של I/O אסינכרוני הוא כפי שהוסבר במאמר סדרת ה-I/O, ובקנה מידה גדול יותר אפשר גם לחבר IOCP או I/O של מאגר תהליכונים.
flowchart TB
accTitle: מבנה של שרת overlapped
accDescr: השלמות של הפעולות האסינכרוניות של כל מופע מתקבלות על מערך אירועים, ומספר קטן של תהליכונים ממתין עם WaitForMultipleObjects ומקדם את המופע שהושלם, ומנתק את מספר התהליכונים ממספר הלקוחות
i1["פעולה אסינכרונית מופע 1"] --> ev["מערך אירועים"]
i2["פעולה אסינכרונית מופע 2"] --> ev
i3["פעולה אסינכרונית מופע 3"] --> ev
ev --> wait["המתנה להשלמה עם WaitForMultipleObjects"]
wait --> proc["קידום המופע שהושלם"]
proc --> wait
איור 5: הצורה ה-overlapped מנתקת את מספר התהליכונים ממספר הלקוחות. הדוגמה הרשמית מריצה את המחזור הזה על תהליכון יחיד.
.NET כמעט מבטל את הבחירה הזו. עם WaitForConnectionAsync / ReadAsync / WriteAsync של NamedPipeServerStream יחד עם async/await, מקבלים יעילות overlapped בקוד פשוט כמו הצורה הסינכרונית.9
// C#: שלד של שרת שמקבל כמה לקוחות
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(); // לשחרר בעצמכם כשיוצאים לפני חיבור
throw;
}
_ = HandleClientAsync(server, token); // הבעלות אחרי החיבור עוברת למטפל
}
PipeOptions.CurrentUserOnly הוא מפרט ש”מתיר חיבורים רק מתהליכים של אותו משתמש”, ברירת מחדל נוחה ובטוחה שחוסכת כתיבת ACL בעצמכם.10 אי אפשר להשתמש בו בהגדרה שחוצה משתמשים (שירות ↔ יישום בסשן משתמש, וכדומה), ולכן במקרה כזה עוברים לתכנון ה-ACL בפרק הבא.
flowchart TB
accTitle: לולאת הקבלה של שרת אסינכרוני ב-.NET
accDescr: לולאת הקבלה יוצרת NamedPipeServerStream, ממתינה לחיבור עם WaitForConnectionAsync, ובהגעה מנתקת את טיפול הלקוח באופן אסינכרוני וחוזרת מיד לקבלה הבאה, כך שחיבורים מקבילים מטופלים בקוד פשוט
mk["יצירת זרם השרת"] --> wc["המתנה עם WaitForConnectionAsync"]
wc --> got["חיבור מגיע"]
got --> hd["ניתוק טיפול הלקוח אסינכרונית"]
hd --> mk
איור 6: לולאת הקבלה נצמדת למחזור “המתן → נתק → הבא”, ועיבוד כל לקוח ממשיך במקביל.
5. אבטחה — ארבעה חובה כששירות בעל הרשאות משתמש בצינורות
הסיבה הגדולה ביותר ש-Named Pipes הם המועמד הראשון ל-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 אחרי העבודה, הפרקטיקות במאמר על טוקני התחזות חלות כמות שהן.
sequenceDiagram
accTitle: זרימת טיפול בבקשה שמשתמשת בהתחזות
accDescr: השרת קורא בקשה מהצינור, מאשר ש-ImpersonateNamedPipeClient הצליח, ואז מבצע את הפעולה בהרשאות הלקוח וחוזר להקשר שלו עם RevertToSelf. אם ההתחזות נכשלת הוא מסרב לבקשה בלי לבצע אותה
participant C as לקוח
participant S as שרת
C->>S: שליחת בקשה
S->>S: קריאת הבקשה
S->>S: ImpersonateNamedPipeClient
Note over S: בכישלון, סירוב בלי לבצע את הבקשה
S->>S: ביצוע הפעולה בהרשאות הלקוח
S->>S: RevertToSelf לשחזור ההקשר
S->>C: תשובה עם התוצאה
איור 7: אישור שההתחזות הצליחה ו-RevertToSelf אמין באים כחבילה. ממשיכים בכישלון — וזה רץ בהרשאות השרת.
flowchart TB
accTitle: ארבע נקודות שמגנות על צינור של שירות בעל הרשאות
accDescr: צד השרת מחזק את הכניסה בדחיית מרוחק, ACL מפורש והבטחת מופע ראשון; צד הלקוח מציין את רמת ההתחזות המינימלית הנדרשת כדי ששרת מזויף לא יוכל לשאול הרשאות (מצמצמים לרמת זיהוי אם התכנון לא נותן לשרת לשאול הרשאות)
subgraph sv["צד השרת"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["הגבלת מתחברים עם ACL"]
r3["FIRST_PIPE_INSTANCE (מופע ראשון בלבד)"]
end
subgraph cl["צד הלקוח"]
r4["ציון רמת ההתחזות המינימלית"]
end
sv --> safe["הצינור כגבול הרשאות"]
cl --> safe
איור 8: בתכנון שבו הצינור הוא גבול ההרשאות, מממשים את שלוש הנקודות בצד השרת ועוד אחת בצד הלקוח כמערכת.
6. מלכודות מעשיות
מירוץ בסדר ההפעלה. אם לקוח מגיע להתחבר לפני שהשרת יצר את הצינור, מתקבלת שגיאת “הצינור לא קיים”. צד הלקוח בונה “לא קיים → המתן קצת ונסה שוב”. ולהפך, העיקרון בצד השרת הוא להתחיל להאזין עם ConnectNamedPipe לפני שהלקוח מתחיל.8
flowchart TB
accTitle: זרימת ניסיון חוזר לחיבור לקוח
accDescr: פותחים את הצינור עם CreateFile; אם הצינור לא קיים ממתינים קצרות ומנסים שוב; אם כל המופעים תפוסים (ERROR_PIPE_BUSY) ממתינים לפנוי עם WaitNamedPipe ואז מנסים שוב; בהצלחה נכנסים לתקשורת
cf["פתיחה עם CreateFile"] --> ok{"התוצאה?"}
ok -->|"הצלחה"| go["התחלת תקשורת"]
ok -->|"הצינור לא קיים"| wait1["המתנה קצרה (השרת לא התחיל)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["המתנה למופע פנוי עם WaitNamedPipe"]
wait1 --> cf
wnp --> cf
איור 9: טיפול החיבור של הלקוח מבדיל בין שני סוגי הכישלון, “לא קיים” ו”מלא”, ומחזיר את שניהם לניסיון חוזר.
זיהוי ניתוק. כשהעמית יוצא, Read/Write נכשלים עם ERROR_BROKEN_PIPE וכדומה. זו לא חריגה; זו תקשורת יומיומית. השרת מזהה את הניתוק, עושה DisconnectNamedPipe למופע, ומתכונן לחיבור הבא; הלקוח מתחבר מחדש — הרעיון של “חיבור מחדש אידמפוטנטי” שתואר במאמר על שינה/התעוררות חל גם כאן.
הנחות על גודל הודעה. מעל הקריאות המפוצלות של מצב הודעות (פרק 3), אם לא מחליטים כחלק מהפרוטוקול “מה מספר הבתים המרבי של הודעה אחת”, עמית זדוני (או באג) יכול לבזבז לכם זיכרון בהודעה ענקית. קבעו תקרה, ונתקו אם היא חורגת; זו הגישה הבטוחה.
השלמת כתיבה וקבלת העמית הם דברים שונים. הצלחה של WriteFile לא אומרת שיישום העמית עיבד את הנתונים. פעולות שדורשות ודאות נשענות על תכנונים כמו אישור בהודעת תשובה, וכללת ההתאמה בין בקשה לתשובה בפרוטוקול.
7. סיכום
- Named Pipes הם המועמד הראשון ל-IPC באותו מחשב. הסיבות הן אותה קלות שימוש כמו I/O של קבצים, והשילוב עם מודל האבטחה של Windows — ACL והתחזות.
- בחירת המצב היא “מצב הודעות לבקשה ותשובה, מצב בתים אם כבר יש framing משלכם”. גם במצב הודעות עדיין צריך לטפל בקריאות מפוצלות (ERROR_MORE_DATA).
- כמה לקוחות הם כמה מופעים + overlapped, או async/await ב-.NET. לעבודה חדשה הצורה האסינכרונית של .NET היא הפשוטה.
- בצינור שהוא גבול הרשאות, קחו כמערכת דחיית מרוחק, ACL מפורש, FIRST_PIPE_INSTANCE (מופע ראשון בלבד), וצמצום רמת ההתחזות בצד הלקוח.
- ב-
ImpersonateNamedPipeClient, בדיקת ערך ההחזרה ו-RevertToSelfהם קו החיים. - ארגו לתוך תכנון הפרוטוקול את “היומיום של התקשורת” — סדר הפעלה, ניתוק, תקרת הודעה, אישור תשובה.
Named Pipes הם API ישן, אבל לשימוש של “לתת לתהליכים לדבר זה עם זה באותו מחשב תוך כיבוד גבולות חשבון Windows”, הם עדיין הכלי הטבעי ביותר לעבודה. נקודות השיפוט התכנוני כמעט ממוצות בהיקף המאמר הזה. אחר כך, כתבו את הפרוטוקול שלכם על דף אחד לפני שמתחילים לממש.
מאמרים קשורים
- בחירת תקשורת בין-תהליכית ב-Windows ── טבלת החלטה ל-Named Pipes / TCP / gRPC / זיכרון משותף / COM
- איך לבודד בפועל “רק את הפעולות שדורשות הרשאות מנהל” ביישום Windows
- טיפול נכון בטוקני התחזות של Windows — שאילת הרשאות לפי תהליכון וחזרה בטוחה
- מעמקי ה-I/O של Windows (חלק 2) — I/O סינכרוני ואסינכרוני: מה OVERLAPPED באמת אומר
- מלכודות זיכרון משותף ופרקטיקות מומלצות
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בתכנון ובמימוש שכוללים תקשורת בין-תהליכית — הפרדת שירות מיישום ממשק, בידוד הרשאות מנהל וכדומה — בהחלפת IPC קיים (זיכרון משותף, שקע ביתי, COM ועוד) ב-Named Pipes, ובסקירות אבטחה של תקשורת צינור של שירות בעל הרשאות. התייעצות שמתחילה מהקפצת תכנון פרוטוקול מוזמנת.
קישורים
-
Microsoft Learn, Named Pipes. על כך ש-Named Pipe הוא ערוץ חד-כיווני או דו-כיווני בין שרת צינור ללקוח צינור אחד או יותר; על כך שכל המופעים חולקים את אותו שם אך יש להם מאגרים ו-handles עצמאיים; ועל כך שאפשר להשתמש בו מתהליכים מקומיים ומרוחקים. ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. על כך שהתחזות מאפשרת לתהליכון השרת לפעול בתוך הרשאות הלקוח; על כך שרמת ההתחזות כברירת מחדל היא SecurityImpersonation; ועל כך שהלקוח יכול לשלוט ברמת ההתחזות עם הדגל SECURITY_SQOS_PRESENT בזמן CreateFile (SECURITY_IDENTIFICATION מתיר זיהוי בלבד). ↩ ↩2
-
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
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. על הדוגמה הרשמית של שרת בתהליכון יחיד שמעבד חיבורים סימולטניים עם כמה לקוחות דרך פעולות overlapped. על הצורה שממתינה על מבנה OVERLAPPED ועל אירוע של כל מופע עם WaitForMultipleObjects ומקדמת את מכונת המצבים של המופע שהושלם, ועל אישור השלמת I/O ממתין עם GetOverlappedResult. ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). על שני מצבי הלקוח המרוחק, PIPE_ACCEPT_REMOTE_CLIENTS (קבלת חיבורים מרוחקים ובדיקתם מול מתאר האבטחה) ו-PIPE_REJECT_REMOTE_CLIENTS (סירוב אוטומטי לחיבורי לקוח מרוחקים). ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). על כך שתהליכון בצד השרת מתחיל התחזות בהקשר האבטחה של לקוח ההודעה האחרונה שנקראה מהצינור; על חזרה עם RevertToSelf אחרי ההשלמה; ועל כך שהמשך אחרי כישלון התחזות גורם לביצוע בהקשר (בעל ההרשאות) של תהליך השרת עצמו, ולכן חייבים תמיד לבדוק את ערך ההחזרה ובכישלון אסור לבצע את בקשת הלקוח. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. על כך שהלקוח פותח את הצינור עם CreateFile; על ERROR_PIPE_BUSY כשכל המופעים תפוסים, והמתנה לפנוי עם WaitNamedPipe; ועל כך שה-handle שנפתח כברירת מחדל הוא קריאת בתים, חוסם ולא-overlapped, ו-SetNamedPipeHandleState יכול לשנות אותו למצב קריאת הודעות. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. על פעולות overlapped דרך ReadFileEx / WriteFileEx, קריאה לא-צורכת דרך PeekNamedPipe, TransactNamedPipe שמבצע שליחת בקשה וקבלת תשובה בקריאה אחת על צינור דו-כיווני מסוג הודעות, ועל כך שקריאה חוסמת לפני שהלקוח מתחיל עלולה לגרום למירוץ. ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). על חיבור וקריאה/כתיבה עם NamedPipeServerStream / NamedPipeClientStream, העברה ביחידות הודעה דרך PipeTransmissionMode.Message, וטיפול בכמה לקוחות עם מתודות אסינכרוניות. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). על הפעלת I/O אסינכרוני עם Asynchronous, ועל כך ש-CurrentUserOnly יכול להתיר חיבורים רק עם תהליכים של אותו משתמש (ואותה רמת העלאת הרשאות). ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. על הרכב זכויות הגישה של Named Pipe; על כך ש-GENERIC_WRITE כולל FILE_CREATE_PIPE_INSTANCE, כך שמתן כתיבה כללית ללקוח מתיר גם יצירת מופע שרת; ועל כך שקריאה וכתיבה של נתונים מוענקות כזכויות גישה נפרדות. ↩
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
Win32 Thread Pool API — מקביליות בלי ליצור תהליכונים, דרך CreateThreadpoolWork
מפזרים קריאות CreateThread בכל הקוד הנייטיב? המאמר מסביר את ה-API של מאגר התהליכונים של Win32 שעוצב מחדש ב-Vista — ארבעת האובייקטים work,...
רשימת בדיקה לטיפול בטוח בתהליכי ילד ביישום Windows
כדי לטפל בבטחה בתהליכי ילד ביישום Windows, תכנון הבעלות על עץ התהליכים ונוהל הסיום חשוב יותר מבחירת ה-API להפעלה. המאמר מסדר את Job Objec...
איך קוראים ל-DLL של C# Native AOT מ-C/C++
פרסום ספריית מחלקות C# כ-DLL נייטיבי באמצעות Native AOT, וקריאה לנקודות הכניסה מסוג UnmanagedCallersOnly מ-C/C++ — סקירה לפי מקום השימוש...
מעמקי הווירטואליזציה של Windows (חלק 2) — זיכרון שאפילו הליבה לא יכולה לראות: איך VBS, HVCI ו-Credential Guard עובדים
בהתקנה נקייה על חומרה תואמת, VBS מופעל כברירת מחדל ומשתמש בהיפרווייזור וב-SLAT כדי ליצור בידוד חזק מהליבה. המאמר מסביר את המבנה של VTL, S...
הפצת יישום Windows בקובץ אחד - הגבול בין בינארי יחיד לתלות במערכת ההפעלה
כשרוצים לרכז יישום Windows ל-EXE אחד, המאמר מסדר את ההבדל בין ריכוז ההפצה לפריט אחד לבין ביטול התלות במערכת ההפעלה, וכולל .NET, C++, W...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- איך בוחרים בין Named Pipes לבין TCP (שקע localhost)?
- לתקשורת בין-תהליכית באותו מחשב, Named Pipe הוא המועמד הראשון. הסיבה היא מודל האבטחה. צינור יכול לשלוט ברמת מערכת ההפעלה ב"מי רשאי להתחבר" באמצעות מתאר אבטחה של Windows (ACL), והשרת יכול לבדוק ולשאול את חשבון ה-Windows של העמית המתחבר עם ImpersonateNamedPipeClient. בניגוד לכך, לפורט TCP ב-localhost יכול להתחבר כל אחד, ולכן צריך לקבוע מי העמית באימות עצמאי. מצד שני, אפשרויות מבוססות TCP עדיפות כשצפויה בהמשך תקשורת מרוחקת, כשמדברים גם עם תהליכים במערכות הפעלה אחרות, או כשרוצים לעשות שימוש חוזר בנכס פרוטוקול קיים כמו gRPC. השיקול הזה מפורט גם במאמר על בחירת תקשורת בין-תהליכית ב-Windows.
- להשתמש במצב בתים או במצב הודעות?
- אם רוצים לטפל ב"כתיבה אחת = יחידת משמעות אחת", מצב הודעות (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) נוח. המקבל יכול לקרוא ביחידות שבהן השולח כתב, כך שאין צורך לנהל את הגבולות בעצמכם. מצב בתים הוא "זרם בתים רציף" כמו TCP, וצריך לתכנן את ה-framing בעצמכם — למשל קידומת אורך. אם מעבירים פרוטוקול שכבר כולל framing (למשל צורה מסורסרת עם קידומת אורך), מצב בתים מתאים. הערה: גם במצב הודעות, אם מאגר הקבלה קטן מההודעה מתקבלת קריאה מפוצלת (ERROR_MORE_DATA), ולכן עדיין צריך לטפל בזה. בנוסף, מצב הקריאה הוא הגדרה לפי-handle, ו-CreateNamedPipe קובע אותו רק בצד השרת. הלקוח חייב לציין PIPE_READMODE_MESSAGE עם SetNamedPipeHandleState אחרי CreateFile. ב-.NET השרת מציין PipeTransmissionMode.Message, והלקוח מגדיר את NamedPipeClientStream.ReadMode ל-Message אחרי החיבור.
- איך בונים שרת שמדבר עם כמה לקוחות במקביל?
- Named Pipe יכול ליצור כמה מופעים תחת אותו שם, ומופע אחד מטפל בלקוח אחד. יש שתי צורות. האחת היא תכנון סינכרוני שמקצה תהליכון לכל לקוח; המימוש פשוט, אבל הוא צורך תהליכון אחד לכל לקוח. האחרת היא I/O אסינכרוני עם FILE_FLAG_OVERLAPPED, שבו מספר קטן של תהליכונים מטפלים ב-ConnectNamedPipe, ReadFile ו-WriteFile לכל המופעים; גם הדוגמה הרשמית של Microsoft מציגה מימוש שמעבד כמה מופעים על תהליכון יחיד. ב-.NET אפשר לכתוב את הצורה האסינכרונית כמעט באותה פשטות כמו הסינכרונית, עם NamedPipeServerStream.WaitForConnectionAsync ו-async/await. אם אין סיבה מיוחדת, הצורה האסינכרונית של .NET היא המומלצת למימושים חדשים.
- מה המינימום שצריך לעשות לאבטחת Named Pipe?
- ארבע נקודות. ראשית, אם אין צורך בחיבורים מרוחקים, ציינו PIPE_REJECT_REMOTE_CLIENTS וסרבו במפורש לחיבורים דרך הרשת. שנית, הגדירו ACL מתאים עם SECURITY_ATTRIBUTES וצמצמו את המשתמשים והקבוצות שרשאים להתחבר (ה-ACL כברירת מחדל רחב מדי לחלק מהשימושים). שלישית, ציינו FILE_FLAG_FIRST_PIPE_INSTANCE ביצירת המופע הראשון, כדי לזהות "חטיפת שם" שבה צינור באותו שם נוצר קודם (אל תשימו את הדגל הזה על המופע השני ואילך). רביעית, לקוח שרק רוצה שהשרת יזהה אותו צריך לציין SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION ב-CreateFile, כדי ששרת מזויף לא יוכל לשאול (להתחזות ל)הרשאותיו. בתכנון ברוקר שבו השרת מבצע גישה אמיתית תחת הרשאות הלקוח, צריך לאפשר התחזות, ולכן משתמשים בהגבלה הזו או לא לפי השאלה אם התכנון נותן לשרת לשאול הרשאות.
- יש נקודות לתשומת לב בשימוש ב-ImpersonateNamedPipeClient?
- החשובה ביותר היא בדיקת ערך ההחזרה. אם ממשיכים אחרי כישלון התחזות, הפעולות הבאות רצות עם ההרשאות של תהליך השרת עצמו (לרוב גבוהות), ופעולות שלא היו אמורות להיות מותרות ללקוח עוברות. גם התיעוד הרשמי קובע במפורש שבכישלון אסור לבצע את בקשת הלקוח. צריך גם לקרוא לזה רק אחרי שקראתם משהו — ההתחזות מתבצעת בהקשר של "ההודעה האחרונה שנקראה מהצינור" — ולחזור באופן אמין להקשר המקורי עם RevertToSelf כשהעבודה מסתיימת. המנגנון סביב התחזות (טוקנים, רמות התחזות, SeImpersonatePrivilege) מכוסה בפירוט במאמר על טוקני התחזות.