היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 22 Aug 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI (ארכיון רשום): 10.5281/zenodo.22176769)
מזהי ה־DOI שלהלן מפנים לגרסאות שנשמרו בעבר בארכיון, ועשויים שלא להתאים לטקסט הנוכחי. להפניה לטקסט הנוכחי, השתמשו בכתובת של דף זה.
Go Komura (2026). Named Pipes בפועל — ה-IPC הסטנדרטי של Windows, מתכנון עד אבטחה. KomuraSoft LLC. https://comcomponent.com/he/blog/windows-named-pipes-practical-guide/
- DOI (ארכיון רשום)
- 10.5281/zenodo.22176769
- DOI (הגרסה האחרונה שנרשמה)
- 10.5281/zenodo.22176770
“רוצים לשלוח פקודות למסך הגדרות מ-service resident.” “רוצים להפריד רק את העבודה שדורשת הרשאות Administrator ל-process נפרד.” כשבונים IPC (inter-process communication) כזה ב-Windows, מה שכדאי לשקול ראשון הוא Named Pipe.
הסיבה שהוא המועמד הראשון לתקשורת באותו מחשב אינה רק קלות הקריאה והכתיבה. אפשר לצמצם מתחברים עם ACL של Windows, ובמידת הצורך לאשר את חשבון ה-Windows של העמית ולהריץ תחת ההרשאות שלו — זה יתרון גדול. עם זאת, יצירת pipe לבדה אינה הופכת את התקשורת לבטוחה; צריך להגדיר חיבור והרשאות.123
המאמר הזה מסדר את התכנון בסדר צורת החיבור → גבולות ההודעה → תצורת server → אבטחה → התנהגות בכשל. קהל היעד הוא מפתחים שכותבים אפליקציות עסקיות ו-services ב-Windows. הוא מעמיק עד החלטות המימוש ב”מועמד הראשון ל-IPC באותו מחשב” שהוצג ב-מאמר טבלת ההחלטה של IPC.
1. קודם המסקנה: חמש החלטות תכנון
לפני שבוחרים API, מחליטים עם מי מדברים, מהי יחידת הנתונים, כמה חיבורים במקביל, הרשאות, ומה קורה בכשל.
| מה מחליטים | בחירה בסיסית | לקרוא בפירוט |
|---|---|---|
| עם מי מתקשרים | ל-Windows processes באותו מחשב, Named Pipe הוא המועמד הראשון. אם חשובים פריסה remote, מערכות הפעלה אחרות, או שימוש בפרוטוקול קיים, שוקלים בסיס TCP | פרק 2 |
| מה נחשב לפריט אחד | אם כתיבה אחת היא פריט אחד, message mode. אם יש framing קיים, byte mode | פרק 3 |
| איך מקבלים כמה clients | מכינים כמה instances באותו שם. למימוש .NET חדש, I/O אסינכרוני ו-async/await הם הפשוטים | פרק 4 |
| למי מתירים חיבור ושאילת הרשאות | מסרבים ל-remote, ACL, הבטחת ה-instance הראשון, ורמת impersonation של ה-client כסט | פרקים 5 ו-6 |
| מה עושים כשהתקשורת נכשלת | כוללים בפרוטוקול המתנת startup, reconnect, תקרת הודעה ואישור תגובה | פרק 7 |
Named Pipe יכול להשתמש בבקרת חיבור ב-ACL וב-impersonation של client כמנגנוני Windows. אם משתמשים ב-TCP ב-localhost, צריך לתכנן בנפרד authentication שמוודא מי העמית. מצד שני, אם רוצים להרחיב בהמשך לתקשורת remote, לדבר גם עם מערכות הפעלה אחרות, או להשתמש בנכסים קיימים כמו gRPC, בסיס TCP עדיף.23
חשוב במיוחד לא לבצע בקשה ש-impersonation שלה נכשל. אם מתעלמים מהכישלון, העיבוד ממשיך בהרשאות ה-server עצמו, לא של ה-client. כשבונים service מועדף, קראו לא רק את דוגמאות הקוד אלא גם את פרקים 5 ו-6.4
2. מנגנון החיבור: השם משותף, נתיב התקשורת לפי client
2.1 להפריד יצירה, חיבור, קריאה וכתיבה
Named Pipe הוא נתיב תקשורת חד-כיווני או דו-כיווני שמזוהה בשם כמו \\.\pipe\MyCompany.MyApp.Control. ה-API הראשון שונה בין server ל-client.1
| שלב | צד ה-server | צד ה-client |
|---|---|---|
| להכין את הנתיב | ליצור instance ב-CreateNamedPipe |
להשתמש בשם ה-pipe שה-server הכין |
| להתחבר | לחכות ל-client ב-ConnectNamedPipe |
לפתוח את אותו שם ב-CreateFile |
| להחליף נתונים | לקרוא ולכתוב ב-ReadFile / WriteFile |
לקרוא ולכתוב ב-ReadFile / WriteFile |
אחרי החיבור שני הצדדים מטפלים בזה כמו I/O של קובץ. מעבר לציון השם, הבנת instances וכיוון מקלה גם על כמה חיבורים ועל שגיאות חיבור.
2.2 instance אחד מטפל ב-client אחד
אפשר ליצור כמה pipes באותו שם, ו-instance אחד הופך לנתיב תקשורת עם client אחד. אותו שם אינו אומר שכל ה-clients חולקים נתיב אחד. מספר ה-instances המרבי מצוין ב-CreateNamedPipe הראשון. לציון תקרה קיים גם PIPE_UNLIMITED_INSTANCES.15
flowchart TB
accTitle: המבנה הבסיסי של Named Pipe
accDescr: ה-server יוצר כמה instances של pipe באותו שם וממתין לחיבור ב-ConnectNamedPipe, וכל client פותח את השם ב-CreateFile ומחזיק נתיב דו-כיווני אחד-על-אחד עם instance אחד
s["שרת"] --> i1["מופע 1"]
s --> i2["מופע 2"]
s --> i3["מופע 3"]
c1["לקוח A"] <--> i1
c2["לקוח B"] <--> i2
c3["לקוח C"] <--> i3
איור 1: דוגמה ל-pipe דו-כיווני. בהכנת כמה instances באותו שם, ה-server מדבר אחד-על-אחד עם כל client.
2.3 “כיוון” הוא שם כפי שנראה מצד ה-server
ציון הגישה של ה-client צריך להתאים לכיוון ה-pipe שה-server יצר. אי-התאמה גורמת ל-CreateFile להיכשל.6
| הכיוון שה-server יוצר | פעולת ה-server | הגישה שה-client מציין |
|---|---|---|
| דו-כיווני | קורא וכותב | אפשר לפתוח לקריאה, לכתיבה, או לשניהם |
| outbound | רק כותב | פותחים לקריאה בלבד |
| inbound | רק קורא | פותחים לכתיבה בלבד |
אם כל ה-instances בשימוש, CreateFile של ה-client מחזיר ERROR_PIPE_BUSY. ממתינים למקום פנוי ב-WaitNamedPipe וקוראים שוב ל-CreateFile. זה כישלון אחר מזה שה-server עוד לא יצר את ה-pipe, ולכן מטפלים בו יחד עם מרוץ סדר ה-startup ב-7.1.6
2.4 גם לשימוש מקומי, מחליטים איך מתייחסים לחיבור remote
Named Pipe יכול לשמש גם לחיבור remote דרך SMB בצורה \\server\pipe\name. עם זאת, בתכנון מודרני כמעט אין סיבה להשתמש בנתיב הזה באופן פעיל, וב-IPC מקומי חשוב לא להשאיר פתוח נתיב שאינכם משתמשים בו. מסרבים במפורש עם PIPE_REJECT_REMOTE_CLIENTS ב-5.1.17
3. בחירת mode: להחליט “עד איפה זה פריט אחד”
3.1 לבקשה ותגובה — message; אם יש גבולות קיימים — byte
ההבדל ב-transfer mode הוא אם ה-pipe שומר את גבולות הכתיבה.5
| Mode | מה שהצד המקבל רואה | שימוש מתאים |
|---|---|---|
byte mode (PIPE_TYPE_BYTE) |
זרם בתים בלי גבולות, כמו TCP | נתונים עם framing כמו קידומת אורך, או העברת stream |
message mode (PIPE_TYPE_MESSAGE וקריאת message) |
יחידה שבה כתיבה אחת היא הודעה אחת | בקשה ותגובה פריט-פריט |
flowchart TB
accTitle: ההבדל בין byte mode ל-message mode
accDescr: ב-byte mode שלוש כתיבות הופכות לזרם בתים בלי גבולות והצד המקבל צריך לחתוך בעצמו, אבל ב-message mode היחידה של כל כתיבה נשמרת ומגיעה לצד המקבל כמו שהיא
bw["byte mode: כתיבת AAA, BB, CCCC"] --> br["הקבלה היא זרם AAABBCCCC"]
br --> bf["את הגבולות (framing) מתכננים בעצמכם"]
mw["message mode: אותן שלוש כתיבות"] --> mr["הקבלה היא שלושה פריטים AAA, BB, CCCC"]
mr --> mf["יחידת הכתיבה נשמרת"]
איור 2: ב-byte mode הצד המקבל מנהל גבולות. ב-message mode אפשר לשמור את יחידת הכתיבה, אבל אין ערובה שקריאה אחת מקבלת את הכול.
אם עוד אין לכם גבולות משלכם ואתם רוצים לטפל ב”כתיבה אחת = בקשה אחת”, message mode נוח. אם כבר משתמשים בצורה מסורסרת עם אורך וכדומה, byte mode בסדר. ב-.NET, PipeTransmissionMode.Message הוא הציון לסוג message.8
ל-pipe דו-כיווני מסוג message יש גם TransactNamedPipe, שמבצע שליחת בקשה וקבלת תגובה בקריאה אחת.9
3.2 סוג message ו-read mode הם הגדרות נפרדות
read mode מוגדר לפי handle. גם אם מציינים PIPE_READMODE_MESSAGE ב-CreateNamedPipe, זו הגדרת צד ה-server. handle ש-client פתח ב-CreateFile הוא כברירת מחדל קריאת byte.6
| מימוש | צד ה-server | צד ה-client |
|---|---|---|
| Win32 | מציינים PIPE_TYPE_MESSAGE ו-PIPE_READMODE_MESSAGE |
אחרי החיבור מציינים PIPE_READMODE_MESSAGE ב-SetNamedPipeHandleState |
| .NET | מציינים PipeTransmissionMode.Message |
אחרי החיבור מגדירים את NamedPipeClientStream.ReadMode ל-Message |
הנקודה היא לא לחשוב שאם רק ה-server הוא מסוג message, גם ה-client יכול לקרוא באותן יחידות.
3.3 גם ב-message mode צריך להמשיך לקרוא
זה שגבול ההודעה נשמר וזה שכל ההודעה נכנסת ל-buffer הקבלה הם שני דברים. אם ה-buffer קטן, ReadFile מחזיר ERROR_MORE_DATA וההודעה מפוצלת. צריך לולאה ששומרת את מה שהתקבל, קוראת את השאר ומשרשרת.56
| תוצאת קריאה | טיפול |
|---|---|
| הצלחה | מטפלים בהודעה כהושלמה |
ERROR_MORE_DATA |
נשאר עוד, אז ממשיכים לקרוא ומשרשרים |
| כל שגיאה אחרת | לא ממשיכים לטפל בבקשה; מטפלים ככשל תקשורת כמו ניתוק |
כש”בקשות קטנות עוברות ורק בקשות גדולות נשברות”, בודקים את המשך הקריאה הזה. גם אי אפשר לשרשר בלי גבול. קובעים גודל מקסימלי להודעה אחת כפרוטוקול, ואם חורגים — מנתקים. זה כדי למנוע בזבוז זיכרון מהודעות ענקיות.
4. תצורת server: להפריד קבלה מטיפול ב-client
4.1 להשוות סוג thread סינכרוני לסוג overlapped
פעולת הבסיס של server היא חזרה על יצירת instance → המתנה לחיבור → קריאה וכתיבה → ניתוק ומעבר לבא. כדי לקבל כמה clients במקביל, מריצים את הזרימה הזו על כמה instances.
| תצורה | מנגנון | יתרון ונקודת זהירות |
|---|---|---|
| סוג thread סינכרוני | מקצים thread אחד לכל instance ומעבדים ב-I/O סינכרוני | הקוד פשוט. עם זאת צורכים thread לכל client, ובעצירה צריך תחבולה כדי לצאת מ-I/O חוסם |
| סוג overlapped (אסינכרוני) | יוצרים עם FILE_FLAG_OVERLAPPED ומוציאים חיבור, קריאה וכתיבה באופן אסינכרוני |
מספר קטן של threads יכול לטפל בהשלמות של כמה instances |
הדוגמה הרשמית של Microsoft שמה את ה-events של כל instance במערך, ממתינה ב-WaitForMultipleObjects, ומעבדת כמה חיבורים ב-thread יחיד. אפשר להפריד את מספר ה-clients ממספר ה-threads. בהיקף גדול אפשר גם לחבר ל-IOCP או ל-I/O של thread pool.10
על מנגנון ה-I/O האסינכרוני עצמו ראו את המאמר על I/O סינכרוני ואסינכרוני.
4.2 למימוש .NET חדש, async/await פשוט
ב-.NET אפשר להשתמש ב-WaitForConnectionAsync / ReadAsync / WriteAsync של NamedPipeServerStream עם async/await. למימוש חדש, אם אין סיבה מיוחדת, הסוג האסינכרוני הזה מומלץ. לולאת הקבלה מעבירה חיבור שהתקבל לטיפול נפרד וחוזרת לקבלת ה-instance הבא.8
הבא הוא שלד לולאת הקבלה. כדוגמה לשימוש בין processes של אותו משתמש, מציינים PipeOptions.Asynchronous ו-PipeOptions.CurrentUserOnly. ב-Windows, CurrentUserOnly מגביל לאותו משתמש ולאותה רמת elevation. זו אינה דוגמה שמחברת service של חשבון אחר ל-UI. במקרה ההוא צריך את תכנון ה-ACL ב-5.2.11
// C#: שלד של server שמקבל כמה 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(); // כשיוצאים לפני חיבור, משמידים בעצמנו
throw;
}
_ = HandleClientAsync(server, token); // אחרי החיבור, הבעלות עוברת ל-handler
}
אם קרתה exception לפני החיבור, צד הקבלה משמיד; אחרי החיבור, צד HandleClientAsync מקבל בעלות על ה-stream. באחרון כוללים לא רק קריאה וכתיבה אלא גם השמדה בסיום.
הקוד הזה הוא שלד שהשמיט את פרוטוקול התקשורת ואת גוף ה-handler. בהפעלה אמיתית מנהלים exceptions וסיום של המשימות שהופרדו, ומשלבים את המשך הקריאה ותקרת הגודל מפרק 3, את האבטחה מפרקים 5 ו-6, ואת הניתוק ואישור התגובה מפרק 7. CurrentUserOnly הוא אפשרות נוחה להגביל מתחברים בלי לכתוב ACL בעצמכם, אבל זה לבדו אינו משלים תכנון ל-service מועדף.
5. אבטחה: שלוש נקודות בצד ה-server ואחת בצד ה-client
יתרון האבטחה של Named Pipe הוא משהו שמקבלים רק כשמגדירים נכון. במיוחד ב-תצורת broker של “service עם הרשאות Administrator + אפליקציית UI עם הרשאות נמוכות”, ה-pipe הוא גבול ההרשאות.
5.1 צד ה-server: לסרב לחיבורי remote מיותרים
אם משתמשים כ-IPC מקומי, מציינים PIPE_REJECT_REMOTE_CLIENTS ב-CreateNamedPipe. clients remote נדחים אוטומטית, כך שסוגרים נתיב של “חשבנו שזה לאפליקציה באותו מחשב, אבל אפשר לפתוח גם מהרשת”.7
5.2 צד ה-server: לצמצם מתחברים וזכויות ב-ACL
מעבירים security descriptor ב-SECURITY_ATTRIBUTES ומציינים במפורש אילו משתמשים וקבוצות רשאים להתחבר. אין ערובה שה-ACL כברירת מחדל חמור מספיק לשימוש הזה.2
קל לפספס כאן את הרשאת הכתיבה שנותנים ל-client. אם נותנים ב-ACL כתיבה כללית (GENERIC_WRITE / FILE_GENERIC_WRITE), נכללת גם זכות שקולה ל-FILE_CREATE_PIPE_INSTANCE. אז client מורשה יכול בעצמו ליצור instance server באותו שם ולחטוף חיבורים הבאים.2
נותנים קריאה וכתיבה בזכויות הפרטניות הנדרשות, ולא מעבירים ל-client זכות יצירת instance. תכנון ACL הוא לא רק “למי מתירים” אלא גם “מה מתירים”.
5.3 צד ה-server: לאשר שה-instance הראשון לא נתפס מראש
אם process זדוני יוצר קודם pipe באותו שם, clients יתחברו ל-server המזויף הזה. ה-server מציין FILE_FLAG_FIRST_PIPE_INSTANCE רק כשיוצרים את ה-instance הראשון, וכך מבטיח שהוא הראשון. אם זה נכשל, חושדים בתפיסה מראש ועוצרים.5
ה-flag הזה מיועד לצינור הראשון שתופס את השם. אם שמים אותו גם על instance שני ואילך, היצירה נכשלת. בלולאת קבלה לכמה clients, אל תחזרו על אותו ציון בכל יצירה.5
עם זאת, זה מנגנון ל-server הלגיטימי לזהות חריגה ב-startup, לא authentication של יעד החיבור מצד ה-client. אם ה-service הלגיטימי אינו נוכח, נשאר מקום לתוקף ליצור קודם pipe באותו שם ולהמתין.
5.4 צד ה-client: להחליט עד כמה ה-server רשאי לשאול הרשאות
כהכנה מול server מזויף, ה-client מצמצם את רמת ה-impersonation למינימום הנדרש. אם התכנון מתיר רק אימות זהות, מציינים SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION ב-CreateFile. ה-server יוכל לאשר את החשבון אבל לא לשאול את ההרשאות לגישה אמיתית.3
| מה רוצים שה-server יעשה | מדיניות צד ה-client |
|---|---|
| רק לאשר את זהות המתחבר | לצמצם לרמת identification (SECURITY_IDENTIFICATION) |
| לבצע גישה אמיתית, כמו לפתוח קובץ בהרשאות ה-client | נדרש היתר לרמת impersonation (SECURITY_IMPERSONATION). אבל רק יחד עם אישור שהחיבור הוא ל-server לגיטימי |
ברמת identification, עיבוד broker שעושה גישה אמיתית בהרשאות ה-client אינו אפשרי. ולהפך, אם מתירים impersonation בלי לאשר את היעד, יש סכנה ש-server מזויף ישאל הרשאות.
העיקרון הוא להתיר את ה-impersonation הנדרש רק כשאפשר לאשר יעד לגיטימי באמצעות הבטחת עליית ה-service או authentication הדדי אחרי החיבור. אל תחשבו שזיהוי התפיסה מראש ב-5.3 מייתר אישור מצד ה-client.
6. שימוש ב-impersonation: לא לבצע בקשה שנכשלה
6.1 לא רק להתחבר — לקרוא את הבקשה ואז לעשות impersonation
ה-API שבו server מאשר את זהות ה-client ומעבד בהרשאותיו הוא ImpersonateNamedPipeClient. היעד הוא הקשר האבטחה של שולח ההודעה האחרונה שנקראה מה-pipe הזה. קודם קוראים את הבקשה, ואחר כך קוראים ל-API.4
Impersonation חל על ה-thread שקרא. אם פותחים קובץ במצב הזה, ההחלטה אם הגישה מותרת נשענת על הרשאות ה-client. גם ב-service מועדף, זה המנגנון ל”לבצע את הפעולה שביקשו, בהרשאות מי שביקש”.3
6.2 קריאה, אישור הצלחה והחזרה למקור כסט אחד
הסדר הנדרש הוא לקרוא את הבקשה → לנסות impersonation → לאשר הצלחה → לפעול בהרשאות ה-client → לחזור להקשר המקורי.
sequenceDiagram
accTitle: זרימת טיפול בבקשה עם impersonation
accDescr: ה-server קורא בקשה מה-pipe, מאשר הצלחה של ImpersonateNamedPipeClient ואז פועל בהרשאות ה-client, וחוזר להקשר שלו ב-RevertToSelf. אם impersonation נכשל, לא מבצעים את הבקשה אלא דוחים
participant C as client
participant S as server
C->>S: שולח בקשה
S->>S: קורא את הבקשה
S->>S: ImpersonateNamedPipeClient
Note over S: אם נכשל, לא מבצעים את הבקשה אלא דוחים
S->>S: מבצעים פעולה בהרשאות ה-client
S->>S: חוזרים למקור ב-RevertToSelf
S->>C: משיבים תוצאה
איור 3: אם impersonation נכשל, לא מבצעים את הבקשה. גם בהצלחה, החזרה למקור ב-RevertToSelf אחרי העבודה היא חלק מהטיפול הרצוף.
אל תמשיכו לטיפול בבקשה בלי לבדוק את ערך ההחזרה של ImpersonateNamedPipeClient. בכישלון לא עברתם להרשאות ה-client, ומשתמשים בהרשאות process ה-server עצמו. אם ה-server הוא חשבון מועדף, גם פעולות שאסורות ל-client יעברו. גם התיעוד של Microsoft קובע במפורש שבכישלון לא לבצע את בקשת ה-client.4
כשהעבודה מסתיימת, חוזרים בוודאות להקשר המקורי ב-RevertToSelf. צריך גם אישור הצלחה וגם סיום. פירוט על tokens, רמות impersonation, SeImpersonatePrivilege וכדומה מכוסה ב-מאמר על impersonation tokens ב-Windows.
7. מלכודות בפועל: לתכנן בהנחה שהתקשורת נקטעת
7.1 להמתין ל-startup ולהמתין למילוי ככישלונות נפרדים
כשאי אפשר להתחבר, מפרידים בין ה-server שעוד לא יצר את ה-pipe לבין כל ה-instances שנוצרו והם בשימוש.69
| מצב | תגובת ה-client |
|---|---|
| ה-pipe אינו קיים | כוללים המתנה ל-startup של ה-server, מחכים קצת ומנסים מחדש |
ERROR_PIPE_BUSY |
ממתינים למקום פנוי ב-WaitNamedPipe ומנסים שוב CreateFile |
| החיבור הצליח | ממשיכים לתקשורת. במידת הצורך מגדירים גם את read mode מ-3.2 |
flowchart TB
accTitle: זרימת ניסיון מחדש של חיבור client
accDescr: פותחים את ה-pipe ב-CreateFile, אם ה-pipe אינו קיים מחכים קצת ומנסים מחדש, אם כל ה-instances בשימוש עם ERROR_PIPE_BUSY ממתינים למקום פנוי ב-WaitNamedPipe ואז מנסים מחדש, ואם הצליח נכנסים לתקשורת
cf["פתיחה ב-CreateFile"] --> ok{"התוצאה?"}
ok -->|"הצלחה"| go["תחילת תקשורת"]
ok -->|"ה-pipe אינו קיים"| wait1["לחכות קצת (ה-server עוד לא עלה)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["להמתין למקום פנוי ב-WaitNamedPipe"]
wait1 --> cf
wnp --> cf
איור 4: “אינו קיים” ו”מלא” הם מצבים שונים. בשניהם מנסים שוב להתחבר אחרי המתנה שמתאימה למצב.
צד ה-server, כעיקרון, מתחיל להאזין ב-ConnectNamedPipe לפני שה-client עולה. גם אז מרוץ סדר startup יכול לקרות, ולכן בונים ניסיון מחדש גם בצד ה-client.9
7.2 ניתוק אינו מצב חריג אלא נתיב טיפול רגיל
כש-process העמית יוצא, קריאה וכתיבה נכשלות עם ERROR_BROKEN_PIPE וכדומה. מטפלים בזה כחלק משגרת התקשורת.
כשה-server מזהה ניתוק, הוא מנתק את ה-instance ב-DisconnectNamedPipe ומתכונן לחיבור הבא. ה-client עושה reconnect. גישת “reconnect אידמפוטנטי” שלא הורס מצב גם בחזרה, שהוסברה ב-מאמר על resume מ-Sleep, תקפה גם כאן.
7.3 להודעות גדולות צריך גם “המשך קריאה” וגם “תקרה”
ERROR_MORE_DATA ב-3.3 הוא טיפול לקריאת הודעה מפוצלת. לעומת זאת גודל הודעה מקסימלי הוא הסכם להגבלת כמות הנתונים שמקבלים. אל תערבבו בין השניים.
אם עמית זדוני, או עמית עם באג, שולח הודעה ענקית, מימוש שממשיך לקרוא בלי גבול מבזבז זיכרון. קובעים תקרה בפרוטוקול, ואם חורגים — מנתקים.
7.4 להבדיל בין הצלחת כתיבה לבין סיום העיבוד אצל העמית
הצלחת WriteFile אינה אומרת שאפליקציית העמית עיבדה את הנתונים. לפעולה שצריך לדעת שבוצעה בוודאות, מאשרים בהודעת תגובה.
גם, כדי שאפשר יהיה להתאים איזו תגובה שייכת לאיזו בקשה, כוללים את הקשר בין בקשה לתגובה בפרוטוקול. ההפרדה בין “נשלח” לבין “העיבוד הסתיים” מובילה לאישור סיום עסקי.
8. סיכום: להחליט בנפרד על נתיב, נתונים והרשאות
Named Pipe הוא המועמד הראשון ל-IPC באותו מחשב, כי הוא משלב קלות טיפול כמו I/O של קובץ עם מודל האבטחה של Windows ב-ACL ו-impersonation. עם זאת, יצירת נתיב תקשורת ותכנון פרוטוקול בטוח שקשה לשבור הם שני דברים.
בתכנון חיבור ונתונים יוצאים מכך ש-instance אחד מטפל ב-client אחד. לבקשה ותגובה בוחרים message mode, ואם יש framing קיים — byte mode, ומגדירים קריאת message גם בצד ה-client. צריך גם טיפול בקריאה מפוצלת וגודל מקסימלי. למימוש .NET חדש שמקבל כמה חיבורים, I/O אסינכרוני ו-async/await פשוטים.
בתכנון הרשאות שמים כסט סירוב remote, ACL מפורש, הבטחת ה-instance הראשון, וצמצום רמת impersonation בצד ה-client. כשמשתמשים ב-impersonation, קוראים את הבקשה ואז קוראים ל-API, בכישלון לא מבצעים, ואחרי העבודה חוזרים ב-RevertToSelf.
לבסוף, כותבים את סדר ה-startup, הניתוק, תקרת ההודעה ואישור התגובה כפרוטוקול שלכם. במקום “זה באותו מחשב אז זה לא ייכשל”, הנקודה המעשית היא להחליט על צורה שיכולה להשתקם בלי לחרוג מהרשאות העמית גם כשהתקשורת נקטעת, ורק אז להיכנס למימוש.
מאמרים קשורים
- איך בוחרים IPC ב-Windows — Named Pipes / TCP / gRPC / shared memory / COM, טבלת החלטה
- UAC ב-Windows: איך מפרידים רק פעולות שדורשות Administrator
- איך מטפלים נכון ב-impersonation tokens ב-Windows — שאילת הרשאות לפי thread והחזרה בטוחה
- Windows I/O לעומק (חלק 2) — I/O סינכרוני ואסינכרוני: המשמעות האמיתית של OVERLAPPED
- מלכודות של shared memory ו-best practices מעשיים
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בתכנון ומימוש שכוללים IPC כמו הפרדת service מאפליקציית UI והפרדת הרשאות Administrator, בהחלפת IPC קיים (shared memory, sockets ייעודיים, COM וכדומה) ב-Named Pipes, ובסקירת אבטחה של תקשורת pipe ב-services מועדפים. אפשר להתייעץ גם משלב גיבוש הפרוטוקול.
קישורים
-
Microsoft Learn, Named Pipes. על כך ש-Named Pipe הוא נתיב תקשורת חד-כיווני או דו-כיווני בין pipe server לבין client אחד או יותר, על כך שכל ה-instances חולקים את אותו שם אבל מחזיקים buffers ו-handles עצמאיים, ועל כך שאפשר להשתמש מ-processes מקומיים ו-remote. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Security and Access Rights. על הרכב זכויות הגישה של Named Pipe, על כך ש-GENERIC_WRITE כולל FILE_CREATE_PIPE_INSTANCE ולכן מתן כתיבה כללית ל-client מתיר גם יצירת instance server, ועל כך שיש לתת קריאה וכתיבה של נתונים בזכויות גישה פרטניות. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Impersonating a Named Pipe Client. על כך ש-impersonation מאפשר ל-thread ה-server לפעול בטווח הרשאות ה-client, על כך שרמת ה-impersonation כברירת מחדל היא SecurityImpersonation, ועל כך שה-client יכול לשלוט ברמת ה-impersonation ב-flag SECURITY_SQOS_PRESENT ב-CreateFile (SECURITY_IDENTIFICATION מתיר רק אימות זהות). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). על כך ש-thread בצד ה-server מתחיל impersonation בהקשר האבטחה של ה-client של ההודעה האחרונה שנקראה מה-pipe, על חזרה ב-RevertToSelf אחרי הסיום, ועל כך שאם ממשיכים אחרי כישלון impersonation רצים בהקשר (המועדף) של process ה-server עצמו, ולכן חייבים לבדוק את ערך ההחזרה ובכישלון לא לבצע את בקשת ה-client. ↩ ↩2 ↩3
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). על כיוון ה-pipe (inbound, outbound, דו-כיווני), סוג byte וסוג message (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) ו-read mode (PIPE_READMODE_MESSAGE), מספר instances מרבי (PIPE_UNLIMITED_INSTANCES), מצב אסינכרוני עם FILE_FLAG_OVERLAPPED, הבטחת ה-instance הראשון עם FILE_FLAG_FIRST_PIPE_INSTANCE, ו-timeout ברירת מחדל ל-WaitNamedPipe. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Named Pipe Client. על כך שה-client פותח את ה-pipe ב-CreateFile, על כך שכשכל ה-instances בשימוש מתקבל ERROR_PIPE_BUSY וממתינים למקום פנוי ב-WaitNamedPipe, ועל כך ש-handle שנפתח כברירת מחדל הוא קריאת byte, חוסם ולא-overlapped, ואפשר לשנות ל-message read mode ב-SetNamedPipeHandleState. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). על שני מצבי client remote: PIPE_ACCEPT_REMOTE_CLIENTS (מקבל חיבור remote ובודק מול security descriptor) ו-PIPE_REJECT_REMOTE_CLIENTS (דוחה אוטומטית חיבורי clients remote). ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). על חיבור וקריאה/כתיבה ב-NamedPipeServerStream / NamedPipeClientStream, העברה ביחידות הודעה עם PipeTransmissionMode.Message, וטיפול בכמה clients בשיטות אסינכרוניות. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. על פעולות overlapped ב-ReadFileEx / WriteFileEx, קריאה בלי הוצאה ב-PeekNamedPipe, TransactNamedPipe שמבצע שליחת בקשה וקבלת תגובה בבת אחת ב-pipe דו-כיווני מסוג message, ועל כך שקריאה חוסמת לפני עליית ה-client עלולה לגרום למרוץ. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. על הדוגמה הרשמית שבה server ב-thread יחיד מעבד חיבורים במקביל לכמה clients בפעולות overlapped. על תצורה שממתינה למבני OVERLAPPED ול-events של כל instance ב-WaitForMultipleObjects ומקדמת את מכונת המצבים של ה-instance שהושלם, ועל אישור השלמת I/O ממתינים ב-GetOverlappedResult. ↩
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). על הפעלת I/O אסינכרוני עם Asynchronous, ועל כך ש-CurrentUserOnly מתיר חיבור רק עם processes של אותו משתמש (ואותה רמת elevation). ↩
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
למה arguments נשברים — כללי command-line arguments ב-Windows
Windows מעביר ל-CreateProcess מחרוזת אחת שהמקבל מפצל. מכסה את כללי CommandLineToArgvW, CRT ו-.NET, ArgumentList, ובניה ב-C++.
מה נשאר אחרי שה-parent מת — מחזיקים child processes ב-Job Object
למה SDK helpers שורדים UI שנהרג ומחזיקים את המצלמה או את ה-COM port? מתכננים משך חיים של child process עם Job Objects, KillOnJobClose ו-c...
Win32 Thread Pool API — מקביליות בלי CreateThread, דרך CreateThreadpoolWork
מפזרים קריאות CreateThread בכל הקוד ה-native? המאמר מסביר את ה-Win32 Thread Pool API שעוצב מחדש ב-Vista: ארבעת האובייקטים work, timer, wa...
DllMain ו-loader lock — הסיבה האמיתית שאומרים לא לעשות כלום ב-initialization של DLL
למה אסור לקרוא ל-LoadLibrary או להסתנכרן עם threads מתוך DllMain. המאמר מסביר ממקורות ראשוניים איך loader lock מסדר כל DLL notification, ...
Spurious wakeup — למה condition variable מתעורר בלי notify, ואיך לחכות נכון ב-Windows
Wait של condition variable יכול להתעורר בלי notify (spurious wakeup). למה Windows מתיר זאת, וצורת ה-wait הנכונה עם while ו-predicate ב-Wi...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- איך בוחרים בין Named Pipes לבין TCP (socket ב-localhost)?
- ל-IPC באותו מחשב, Named Pipe הוא המועמד הראשון. הסיבה היא מודל האבטחה. pipe יכול לשלוט ברמת ה-OS במי רשאי להתחבר באמצעות security descriptor של Windows (ACL), וה-server יכול לבדוק ולשאול את חשבון ה-Windows של העמית שמתחבר עם ImpersonateNamedPipeClient. בניגוד לזה, לפורט TCP ב-localhost יכול להתחבר כל אחד, ולכן צריך לקבוע מי העמית ב-authentication עצמאי. מצד שני, אפשרויות מבוססות TCP עדיפות כשצפויה בהמשך תקשורת remote, כשמדברים גם עם processes במערכות הפעלה אחרות, או כשרוצים לעשות שימוש חוזר בנכס פרוטוקול קיים כמו gRPC. השיקול הזה מפורט גם במאמר על טבלת ההחלטה של IPC.
- להשתמש ב-byte mode או ב-message mode?
- אם רוצים לטפל בכתיבה אחת כיחידת משמעות אחת, message mode (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) נוח. הצד שמקבל יכול לקרוא ביחידות שבהן השולח כתב, כך שאין צורך לנהל את הגבולות בעצמכם. byte mode הוא זרם בתים רציף כמו TCP, וצריך לתכנן את ה-framing בעצמכם, למשל קידומת אורך. אם מעבירים פרוטוקול שכבר כולל framing (למשל צורה מסורסרת עם קידומת אורך), byte mode מתאים. הערה: גם ב-message mode, אם buffer הקבלה קטן מההודעה מתקבלת קריאה מפוצלת (ERROR_MORE_DATA), ולכן עדיין צריך לטפל בזה. בנוסף, read mode הוא הגדרה לפי handle, ו-CreateNamedPipe קובע אותו רק בצד ה-server. ה-client חייב לציין PIPE_READMODE_MESSAGE עם SetNamedPipeHandleState אחרי CreateFile. ב-.NET ה-server מציין PipeTransmissionMode.Message, וה-client מגדיר את NamedPipeClientStream.ReadMode ל-Message אחרי החיבור.
- איך בונים server שמדבר עם כמה clients במקביל?
- Named Pipe יכול ליצור כמה instances תחת אותו שם, ו-instance אחד מטפל ב-client אחד. יש שתי צורות. האחת היא תכנון סינכרוני שמקצה thread לכל client; המימוש פשוט, אבל הוא צורך thread אחד לכל client. האחרת היא I/O אסינכרוני עם FILE_FLAG_OVERLAPPED, שבו מספר קטן של threads מטפל ב-ConnectNamedPipe, ReadFile ו-WriteFile לכל ה-instances; גם הדוגמה הרשמית של Microsoft מציגה מימוש שמעבד כמה instances על thread יחיד. ב-.NET אפשר לכתוב את הצורה האסינכרונית כמעט באותה פשטות כמו הסינכרונית, עם NamedPipeServerStream.WaitForConnectionAsync ו-async/await. אם אין סיבה מיוחדת, הצורה האסינכרונית של .NET היא המומלצת למימושים חדשים.
- מה המינימום שצריך לעשות לאבטחת Named Pipe?
- ארבע נקודות. ראשית, אם אין צורך בחיבורים remote, ציינו PIPE_REJECT_REMOTE_CLIENTS וסרבו במפורש לחיבורים דרך הרשת. שנית, הגדירו ACL מתאים עם SECURITY_ATTRIBUTES וצמצמו את המשתמשים והקבוצות שרשאים להתחבר (ה-ACL כברירת מחדל רחב מדי לחלק מהשימושים). שלישית, ציינו FILE_FLAG_FIRST_PIPE_INSTANCE ביצירת ה-instance הראשון, כדי לזהות hijack של השם שבו pipe באותו שם נוצר קודם (אל תשימו את ה-flag הזה על ה-instance השני ואילך). רביעית, client שרק רוצה שה-server יזהה אותו צריך לציין SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION ב-CreateFile, כדי ש-server מזויף לא יוכל לעשות impersonation להרשאותיו. בתכנון broker שבו ה-server מבצע גישה אמיתית תחת הרשאות ה-client, צריך לאפשר impersonation, ולכן משתמשים בהגבלה הזו או לא לפי השאלה אם התכנון נותן ל-server לשאול הרשאות.
- יש נקודות לתשומת לב בשימוש ב-ImpersonateNamedPipeClient?
- החשובה ביותר היא בדיקת ערך ההחזרה. אם ממשיכים אחרי כישלון impersonation, הפעולות הבאות רצות עם ההרשאות של process ה-server עצמו (לרוב גבוהות), ופעולות שלא היו אמורות להיות מותרות ל-client עוברות. גם התיעוד הרשמי קובע במפורש שבכישלון אסור לבצע את בקשת ה-client. צריך גם לקרוא לזה רק אחרי שקראתם משהו: ה-impersonation מתבצע בהקשר של ההודעה האחרונה שנקראה מה-pipe, ולחזור באופן אמין להקשר המקורי עם RevertToSelf כשהעבודה מסתיימת. המנגנון סביב impersonation (tokens, רמות impersonation, SeImpersonatePrivilege) מכוסה בפירוט במאמר על impersonation tokens.