«Quiero que un servicio residente y una interfaz de configuración intercambien comandos.» «Quiero aislar en un proceso distinto solo el trabajo que necesita privilegios de administrador.» «Quiero que las herramientas del mismo PC se pasen datos entre sí.» — Cuando en Windows hace falta este tipo de comunicación entre procesos (IPC), el estándar que conviene considerar primero es una canalización con nombre.
El artículo sobre cómo elegir la comunicación entre procesos en Windows situó las canalizaciones con nombre como «la primera candidata para la IPC en la misma máquina». Este artículo es el tratamiento detallado. Por qué son la primera candidata, cómo se eligen los modos y las formas de servidor, y qué hay que proteger cuando las usa un servicio privilegiado —dirigido a desarrolladores que escriben aplicaciones empresariales y servicios en Windows, organiza el material para esos juicios de diseño a partir de fuentes primarias.
1. Conclusión principal
- Una canalización con nombre es un canal bidireccional entre procesos con un espacio de nombres de la forma
\\.\pipe\name. Se pueden crear varias instancias bajo el mismo nombre y aceptar varios clientes a la vez.1 - La razón de que sean la primera candidata para la IPC en la misma máquina es el modelo de seguridad. Se puede controlar quién se conecta con un ACL, y el servidor puede inspeccionar y tomar prestada (suplantar) la cuenta de Windows del cliente. El TCP localhost no tiene ninguna de las dos cosas.2
- Si se quiere tratar «una escritura = un mensaje», use el modo mensaje; si ya tiene su propio framing, use el modo byte. Incluso en modo mensaje hay que tratar las lecturas partidas cuando el búfer es corto (ERROR_MORE_DATA).3
- Atienda a varios clientes con «varias instancias + E/S superpuesta» o con «async/await de .NET». El ejemplo oficial muestra una forma que procesa varias instancias en un solo hilo.4
- El mínimo de seguridad son cuatro puntos: rechazar lo remoto (PIPE_REJECT_REMOTE_CLIENTS), hacer explícito el ACL, detectar el secuestro con FILE_FLAG_FIRST_PIPE_INSTANCE y minimizar el nivel de suplantación en el lado cliente.56
- Para
ImpersonateNamedPipeClient, comprobar el valor de retorno es el salvavidas. Si se ignora un fallo, el procesamiento sigue con los privilegios del servidor.6
2. Qué es una canalización con nombre — Espacio de nombres, instancias y cómo funcionan las conexiones
Una canalización con nombre es un canal identificado por un nombre como \\.\pipe\MyCompany.MyApp.Control. El servidor la crea con CreateNamedPipe y el cliente abre el mismo nombre con CreateFile. Una vez abierta, ambos lados leen y escriben con ReadFile / WriteFile: lo distintivo es que se puede usar con la misma forma que la E/S de archivos.1
El concepto importante es la instancia. Se pueden crear varias instancias de una canalización con el mismo nombre, y una instancia es un canal con un cliente. La primera llamada a CreateNamedPipe decide el número máximo de instancias (o ilimitado).3
La conexión en el lado cliente tiene una receta estándar. Cuando todas las instancias están en uso, CreateFile falla con ERROR_PIPE_BUSY, así que se espera a que haya una libre con WaitNamedPipe y se reintenta. Además, el acceso que se indica al abrir tiene que coincidir con la dirección que creó el servidor: una canalización bidireccional se puede abrir indicando lectura o escritura, pero una canalización de salida que el servidor solo escribe debe abrirse en solo lectura, y una de entrada que el servidor solo lee debe abrirse en solo escritura, o CreateFile falla.7
flowchart TB
accTitle: Dirección de la canalización y acceso del cliente
accDescr: Un cliente puede abrir una canalización bidireccional indicando lectura o escritura, pero debe abrir como solo lectura una de salida que el servidor solo escribe, y como solo escritura una de entrada que el servidor solo lee
q{"¿Dirección que creó el servidor?"} -->|"Bidireccional"| dc["Vale lectura o escritura"]
q -->|"Salida"| oc["Abrir en solo lectura"]
q -->|"Entrada"| ic["Abrir en solo escritura"]
Figura 1: Un desajuste entre dirección y especificación de acceso se convierte en un fallo de CreateFile. Al investigar un error de conexión, compruebe esto primero.
flowchart TB
accTitle: Estructura básica de una canalización con nombre
accDescr: El servidor crea varias instancias de canalización con el mismo nombre y espera una conexión con ConnectNamedPipe; cada cliente abre el nombre con CreateFile y tiene un canal bidireccional uno a uno con una instancia
s["Servidor"] --> i1["Instancia 1"]
s --> i2["Instancia 2"]
s --> i3["Instancia 3"]
c1["Cliente A"] <--> i1
c2["Cliente B"] <--> i2
c3["Cliente C"] <--> i3
Figura 2: Al mantener varias instancias del mismo nombre, un servidor puede hablar uno a uno con varios clientes al mismo tiempo.
Las canalizaciones con nombre también se pueden abrir en remoto por SMB (\\server\pipe\name), pero en un diseño moderno casi no hay motivo para usarlo de forma activa; el problema es más bien no dejarlo abierto cuando no se usa (capítulo 5).
3. Modo byte y modo mensaje
Una canalización tiene dos modos de transferencia.3
- Modo byte (PIPE_TYPE_BYTE): un «flujo de bytes ininterrumpido» como TCP. Uno decide por sí mismo dónde termina un mensaje (se diseña el framing, por ejemplo un prefijo de longitud).
- Modo mensaje (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): una escritura se trata como un mensaje, y el lector lo recibe en esa unidad. Esto es más fácil para intercambios de petición/respuesta.
El modo mensaje también tiene un compañero cómodo, TransactNamedPipe, que envía una petición y recibe la respuesta en una sola llamada.8 Hay, no obstante, una trampa. Si el búfer de recepción es más pequeño que el mensaje completo, la lectura devuelve ERROR_MORE_DATA y se convierte en una lectura partida. No asuma que el modo mensaje significa «un Read siempre trae el todo»; sigue haciendo falta un bucle que lea el resto. Tenga en cuenta que el modo de lectura es un ajuste por identificador, y CreateNamedPipe lo decide solo en el lado servidor. El cliente lo indica con SetNamedPipeHandleState después de CreateFile (en .NET, ReadMode después de conectar).7
flowchart TB
accTitle: Bucle de lectura partida en modo mensaje
accDescr: Si ReadFile tiene éxito el mensaje está completo; si devuelve ERROR_MORE_DATA se lee el resto que no cupo en el búfer y se concatena; cualquier otro error se trata como una desconexión
read["Leer con ReadFile"] --> r{"¿Resultado?"}
r -->|"Éxito"| done["Mensaje completo"]
r -->|"ERROR_MORE_DATA"| more["Leer el resto y concatenar"]
more --> read
r -->|"Cualquier otro error"| dis["Tratar como desconexión"]
Figura 3: Incluso en modo mensaje hace falta un «bucle de leer el resto»; sin él, solo se rompen los mensajes grandes.
flowchart TB
accTitle: Diferencia entre modo byte y modo mensaje
accDescr: En modo byte tres escrituras se convierten en un flujo de bytes ininterrumpido y el receptor tiene que partirlo; en modo mensaje se conserva la unidad de cada escritura y llega al receptor tal cual
bw["Modo byte: escribe AAA, BB, CCCC"] --> br["Se recibe el flujo AAABBCCCC"]
br --> bf["El framing lo diseña usted"]
mw["Modo mensaje: las mismas tres escrituras"] --> mr["Se reciben tres mensajes: AAA, BB, CCCC"]
mr --> mf["Se conservan las unidades de escritura"]
Figura 4: El modo mensaje conserva «la unidad de una escritura» y la entrega. El diseño de framing deja de ser necesario; solo no olvide tratar las lecturas partidas.
La regla práctica para elegir es sencilla. Si el intercambio tiene forma de «petición y respuesta», modo mensaje. Si se transporta una forma que ya lleva el framing incorporado (datos serializados con prefijo de longitud o una transferencia en flujo), use el modo byte. En .NET, indicar PipeTransmissionMode.Message corresponde a lo primero.9
4. Diseño del servidor — ¿Un hilo por cliente, o superpuesto?
La operación básica del servidor es el bucle «crear una instancia → esperar a un cliente con ConnectNamedPipe → leer y escribir → desconectar e ir al siguiente cliente». Hay dos formas de hablar con varios clientes a la vez.
Síncrono, un hilo por instancia. Se asigna un hilo a cada instancia, y cada uno habla con su cliente con E/S síncrona. El código es directo, pero se consume un hilo por cliente, y además hace falta una forma de salir de la E/S bloqueante al apagar el conjunto.
Superpuesto (asíncrono). Se crean instancias con FILE_FLAG_OVERLAPPED, se emiten ConnectNamedPipe / ReadFile / WriteFile de forma asíncrona, y un número reducido de hilos gestiona la finalización de todas las instancias. El ejemplo oficial de Microsoft muestra un servidor que espera en una matriz de eventos con WaitForMultipleObjects y procesa varias instancias en un solo hilo.4 El relato general de la E/S asíncrona es el de el artículo de la serie de E/S, y a mayor escala también se puede acoplar IOCP o E/S del grupo de hilos.
flowchart TB
accTitle: Estructura de un servidor superpuesto
accDescr: Las finalizaciones de las operaciones asíncronas de cada instancia se reciben en una matriz de eventos, y unos pocos hilos esperan con WaitForMultipleObjects y avanzan la instancia completada, desacoplando el número de hilos del de clientes
i1["Op. asíncrona instancia 1"] --> ev["Matriz de eventos"]
i2["Op. asíncrona instancia 2"] --> ev
i3["Op. asíncrona instancia 3"] --> ev
ev --> wait["Esperar finalización con WaitForMultipleObjects"]
wait --> proc["Avanzar la instancia completada"]
proc --> wait
Figura 5: La forma superpuesta desacopla el número de hilos del de clientes. El ejemplo oficial hace girar este ciclo en un solo hilo.
.NET casi elimina esta elección. Usando WaitForConnectionAsync / ReadAsync / WriteAsync de NamedPipeServerStream junto con async/await, se obtiene la eficiencia superpuesta en un código tan directo como la forma síncrona.9
// C#: esqueleto de un servidor que acepta varios clientes
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 es una especificación que «permite conexiones solo desde procesos del mismo usuario», un valor predeterminado cómodo y seguro que ahorra escribir un ACL uno mismo.10 No se puede usar en una configuración que cruza usuarios (un servicio ↔ una aplicación en una sesión de usuario, y similares), así que en ese caso se pasa al diseño de ACL del capítulo siguiente.
flowchart TB
accTitle: Bucle de aceptación de un servidor asíncrono .NET
accDescr: El bucle de aceptación crea un NamedPipeServerStream, espera una conexión con WaitForConnectionAsync y, al llegar, despega el manejo del cliente de forma asíncrona y vuelve de inmediato a la siguiente aceptación, de modo que las conexiones concurrentes se gestionan con código directo
mk["Crear el flujo servidor"] --> wc["Esperar con WaitForConnectionAsync"]
wc --> got["Llega la conexión"]
got --> hd["Despegar el manejo del cliente"]
hd --> mk
Figura 6: El bucle de aceptación se atiene al ciclo «esperar → despegar → siguiente», y el procesamiento de cada cliente avanza en paralelo.
5. Seguridad — Cuatro obligaciones cuando un servicio privilegiado usa canalizaciones
La razón más importante de que las canalizaciones con nombre sean la primera candidata para la IPC en la misma máquina es el modelo de seguridad, pero eso solo vale si se configura correctamente. Sobre todo en un diseño de broker de «un servicio con privilegios de administrador + una aplicación de interfaz de privilegios bajos», la canalización es la frontera de privilegios en sí. Hay cuatro puntos que conviene fijar.
(1) Rechazar lo remoto. Que una canalización pensada como IPC local se pueda abrir desde la red es, por sí solo, superficie de ataque. Si se indica PIPE_REJECT_REMOTE_CLIENTS en CreateNamedPipe, las conexiones de clientes remotos se rechazan automáticamente.5
(2) Hacer explícito el ACL. Se pasa un descriptor de seguridad en SECURITY_ATTRIBUTES y se restringen los usuarios y grupos autorizados a conectar. No dé al cliente GENERIC_WRITE: el derecho FILE_CREATE_PIPE_INSTANCE incluido en él permitiría que un cliente autorizado creara él mismo una instancia servidor del mismo nombre y robara las conexiones siguientes. Conceda lectura y escritura como derechos individuales, y no pase el derecho de crear instancias.11
(3) Impedir el secuestro de nombre. Los nombres de canalización son por orden de llegada. Si un proceso malicioso crea primero una canalización del mismo nombre y espera, los clientes se conectan al servidor falso. El servidor indica FILE_FLAG_FIRST_PIPE_INSTANCE al crear la primera instancia, garantizando que «yo soy el primero», y si eso falla sospecha un secuestro y se detiene. Esta marca es solo para la primera instancia que reclama el nombre; ponerla en la segunda y posteriores hace fallar la creación.3
| (4) El cliente minimiza el nivel de suplantación a lo necesario. Esto es una preparación para el caso en que el interlocutor sea un servidor falso. Si el cliente indica **SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION** en CreateFile, el servidor puede identificar al cliente pero no tomar prestados esos privilegios y actuar.2 Es, no obstante, un compromiso frente a un flujo de suplantación: en un diseño de broker en el que el servidor realiza un acceso real con los privilegios del cliente, el nivel de identificación no basta para que la suplantación tenga éxito, y hay que permitir SECURITY_IMPERSONATION. Esa autorización está condicionada a estar seguro de que se está conectado al servidor genuino. La protección contra el secuestro en el lado servidor es solo un mecanismo que se entera por un arranque fallido; si el servicio genuino no está y un atacante crea primero la canalización del mismo nombre, el cliente aún puede conectarse al servidor falso. Permítalo solo cuando pueda confirmar al interlocutor mediante un arranque garantizado del servicio o una autenticación mutua después de conectar. |
La comprobación de identidad y el préstamo de privilegios en el lado servidor es ImpersonateNamedPipeClient. Llámese esto después de leer una petición de la canalización y el hilo que llama empieza a ejecutarse en el contexto de seguridad del emisor del último mensaje leído. Abrir un archivo con los privilegios del cliente hace que la comprobación de acceso se haga contra el cliente: el mecanismo por el que un servicio privilegiado ejecuta «la operación pedida, con los privilegios de quien la pide».6 La condición absoluta para usarlo es comprobar el valor de retorno. Continuar después de que la suplantación falle hace que las operaciones siguientes se ejecuten con los privilegios altos propios del servidor. La documentación oficial indica de forma explícita que «si falla, no se debe ejecutar la petición del cliente». Junto con RevertToSelf después del trabajo, las prácticas del artículo sobre tokens de suplantación se aplican tal cual.
sequenceDiagram
accTitle: Flujo de manejo de una petición que usa suplantación
accDescr: El servidor lee una petición de la canalización, confirma que ImpersonateNamedPipeClient tuvo éxito, realiza la operación con los privilegios del cliente y vuelve a su propio contexto con RevertToSelf. Si la suplantación falla, rechaza la petición sin ejecutarla
participant C as Cliente
participant S as Servidor
C->>S: Enviar una petición
S->>S: Leer la petición
S->>S: ImpersonateNamedPipeClient
Note over S: Si falla, rechazar sin ejecutar la petición
S->>S: Realizar la operación con los privilegios del cliente
S->>S: RevertToSelf para restaurar el contexto
S->>C: Responder con el resultado
Figura 7: Confirmar que la suplantación tuvo éxito y un RevertToSelf fiable van en el mismo paquete. Continuar si falla y se ejecuta con los privilegios del servidor.
flowchart TB
accTitle: Cuatro puntos que protegen la canalización de un servicio privilegiado
accDescr: El lado servidor endurece la entrada con rechazo remoto, un ACL explícito y una garantía de primera instancia; el lado cliente indica el nivel mínimo de suplantación necesario para que un servidor falso no pueda tomar prestados privilegios (restringirlo a identificación si el diseño no deja que el servidor tome prestados privilegios)
subgraph sv["Lado servidor"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["Restringir conectores con un ACL"]
r3["FIRST_PIPE_INSTANCE (solo 1.ª)"]
end
subgraph cl["Lado cliente"]
r4["Indicar el nivel mínimo de suplantación"]
end
sv --> safe["La canalización como frontera de privilegios"]
cl --> safe
Figura 8: En un diseño en el que la canalización es la frontera de privilegios, implemente los tres puntos del lado servidor más el del lado cliente como un conjunto.
6. Trampas prácticas
Una carrera en el orden de arranque. Si un cliente llega a conectar antes de que el servidor haya creado la canalización, se produce un error de «la canalización no existe». El lado cliente incorpora «no existe → esperar un poco y reintentar». A la inversa, el principio en el lado servidor es empezar a escuchar con ConnectNamedPipe antes de que arranque el cliente.8
flowchart TB
accTitle: Flujo de reintento de conexión del cliente
accDescr: Abrir la canalización con CreateFile; si no existe esperar un momento y reintentar; si todas las instancias están en uso (ERROR_PIPE_BUSY) esperar a una libre con WaitNamedPipe y luego reintentar; si tiene éxito, entrar en comunicación
cf["Abrir con CreateFile"] --> ok{"¿Resultado?"}
ok -->|"Éxito"| go["Empezar a comunicar"]
ok -->|"No existe"| wait1["Esperar (servidor no listo)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["WaitNamedPipe (instancia libre)"]
wait1 --> cf
wnp --> cf
Figura 9: El manejo de conexión del cliente distingue los dos tipos de fallo, «no existe» y «llena», y ambos vuelven a un reintento.
Detectar una desconexión. Cuando el interlocutor sale, Read/Write fallan con ERROR_BROKEN_PIPE y similares. Eso no es una anomalía; es el día a día de la comunicación. El servidor detecta la desconexión, hace DisconnectNamedPipe de la instancia y se prepara para la siguiente conexión; el cliente se reconecta: la idea de «reconexión idempotente» descrita en el artículo sobre suspensión/reanudación se aplica también aquí.
Supuestos sobre el tamaño del mensaje. Además de las lecturas partidas del modo mensaje (capítulo 3), si no se decide como parte del protocolo «cuál es el máximo de bytes de un mensaje», un interlocutor malicioso (o defectuoso) puede desperdiciar memoria con un mensaje enorme. Decidir un tope y desconectar si se supera es el enfoque seguro.
La finalización de la escritura y que el interlocutor reciba son cosas distintas. El éxito de WriteFile no significa que la aplicación del interlocutor haya procesado los datos. Las operaciones que necesitan certeza se respaldan con diseños como confirmar con un mensaje de respuesta e incluir la correspondencia de petición y respuesta en el protocolo.
7. Resumen
- Las canalizaciones con nombre son la primera candidata para la IPC en la misma máquina. Las razones son la misma facilidad de uso que la E/S de archivos, y la integración con el modelo de seguridad de Windows de ACL y suplantación.
- La elección de modo es «modo mensaje para petición y respuesta, modo byte si ya tiene su propio framing». Incluso en modo mensaje hay que tratar las lecturas partidas (ERROR_MORE_DATA).
- Varios clientes son varias instancias + superpuesto, o async/await de .NET. Para trabajo nuevo, la forma asíncrona de .NET es la más directa.
- En una canalización que es frontera de privilegios, tome como conjunto el rechazo remoto, un ACL explícito, FIRST_PIPE_INSTANCE (solo la primera instancia) y minimizar el nivel de suplantación en el lado cliente.
- Para
ImpersonateNamedPipeClient, comprobar el valor de retorno yRevertToSelfson el salvavidas. - Teja en el diseño del protocolo el «día a día de la comunicación»: orden de arranque, desconexión, tope de mensaje, confirmación de respuesta.
Las canalizaciones con nombre son una API antigua, pero para el uso de «hacer que los procesos hablen entre sí en la misma máquina respetando los límites de las cuentas de Windows», siguen siendo la herramienta más natural para el trabajo. Los puntos de juicio de diseño casi se agotan en el alcance de este artículo. Después, escriba su propio protocolo en una sola hoja antes de empezar a implementar.
Artículos relacionados
- Cómo elegir la comunicación entre procesos en Windows — canalizaciones con nombre / TCP / gRPC / memoria compartida / COM: tabla de decisión
- Windows: cómo separar en el código «solo los procesos que necesitan permisos de administrador»
- Cómo manejar correctamente los tokens de suplantación de Windows — préstamo de privilegios por hilo y una forma segura de revertirlos
- Las profundidades de la E/S de Windows (Parte 2) ── E/S síncrona y E/S asíncrona: el verdadero significado de OVERLAPPED
- Trampas de la memoria compartida y buenas prácticas para producción
Áreas de consultoría relacionadas
En KomuraSoft LLC nos encargamos del diseño e implementación que involucran comunicación entre procesos —separar un servicio de una aplicación de interfaz, aislar privilegios de administrador y similares—, de sustituir IPC existente (memoria compartida, un socket casero, COM, etc.) por canalizaciones con nombre, y de revisiones de seguridad de la comunicación por canalización de un servicio privilegiado. La consulta que empieza por contrastar un diseño de protocolo es bienvenida.
- Desarrollo de aplicaciones Windows
- Consultoría técnica y revisión de diseño
- Investigación de fallos y análisis de causas
- Contacto
Referencias
-
Microsoft Learn, Named Pipes. Sobre que una canalización con nombre es un canal unidireccional o bidireccional entre un servidor de canalización y uno o más clientes; sobre que todas las instancias comparten el mismo nombre pero tienen búferes e identificadores independientes; y sobre que se puede usar desde procesos locales y remotos. ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. Sobre que la suplantación deja que el hilo del servidor opere dentro de los privilegios del cliente; sobre que el nivel de suplantación predeterminado es SecurityImpersonation; y sobre que el cliente puede controlar el nivel de suplantación con la marca SECURITY_SQOS_PRESENT en el momento de CreateFile (SECURITY_IDENTIFICATION permite solo la identificación). ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sobre la dirección de la canalización (entrada, salida, bidireccional), el tipo byte y el tipo mensaje (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) y el modo de lectura (PIPE_READMODE_MESSAGE), el número máximo de instancias (PIPE_UNLIMITED_INSTANCES), el modo asíncrono mediante FILE_FLAG_OVERLAPPED, la garantía de primera instancia mediante FILE_FLAG_FIRST_PIPE_INSTANCE, y el tiempo de espera predeterminado de WaitNamedPipe. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. Sobre el ejemplo oficial de un servidor de un solo hilo que procesa conexiones simultáneas con varios clientes mediante operaciones superpuestas. Sobre la forma que espera en la estructura OVERLAPPED y el evento de cada instancia con WaitForMultipleObjects y avanza la máquina de estados de la instancia completada, y sobre confirmar la finalización de la E/S pendiente con GetOverlappedResult. ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sobre los dos modos de cliente remoto, PIPE_ACCEPT_REMOTE_CLIENTS (aceptar conexiones remotas e inspeccionarlas contra el descriptor de seguridad) y PIPE_REJECT_REMOTE_CLIENTS (rechazar automáticamente las conexiones de clientes remotos). ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). Sobre que un hilo del lado servidor comienza la suplantación en el contexto de seguridad del cliente del último mensaje leído de la canalización; sobre volver con RevertToSelf tras terminar; y sobre que continuar después de que la suplantación falle provoca la ejecución en el contexto propio (privilegiado) del proceso servidor, de modo que hay que comprobar siempre el valor de retorno y, si falla, no ejecutar la petición del cliente. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. Sobre que el cliente abre la canalización con CreateFile; sobre ERROR_PIPE_BUSY cuando todas las instancias están en uso, esperando a una libre con WaitNamedPipe; y sobre que el identificador abierto parte de lectura en byte, bloqueante y no superpuesta, y SetNamedPipeHandleState puede cambiarlo a modo de lectura de mensaje. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. Sobre operaciones superpuestas mediante ReadFileEx / WriteFileEx, una lectura no consumidora mediante PeekNamedPipe, TransactNamedPipe realizando el envío de la petición y la recepción de la respuesta en una sola llamada en una canalización bidireccional de tipo mensaje, y sobre que una lectura bloqueante antes de que arranque el cliente puede provocar una carrera. ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). Sobre conectar y leer/escribir con NamedPipeServerStream / NamedPipeClientStream, la transferencia por unidades de mensaje mediante PipeTransmissionMode.Message, y el manejo de varios clientes con métodos asíncronos. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Sobre habilitar la E/S asíncrona con Asynchronous, y sobre que CurrentUserOnly puede permitir conexiones solo con procesos del mismo usuario (y el mismo nivel de elevación). ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. Sobre la composición de los derechos de acceso de una canalización con nombre; sobre que GENERIC_WRITE incluye FILE_CREATE_PIPE_INSTANCE, de modo que dar a un cliente escritura genérica también permite crear una instancia servidor; y sobre que la lectura y la escritura de datos se conceden como derechos de acceso individuales. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
La API del grupo de hilos de Win32 — Concurrencia sin crear hilos, con CreateThreadpoolWork
¿Dispersa llamadas a CreateThread por todo el código nativo? Este artículo explica la API del grupo de hilos de Win32 rediseñada en Vista...
Lista de verificación para manejar procesos secundarios de forma segura en aplicaciones de Windows
Para manejar procesos secundarios de forma segura en aplicaciones de Windows, la propiedad del árbol de procesos y el procedimiento de ci...
Trampas de la memoria compartida y buenas prácticas para producción
Analizamos las trampas de usar memoria compartida en producción y el diseño que reduce la tasa de incidentes: sincronización, visibilidad...
Cómo interpretar los códigos de error de Windows — la estructura de tres capas de Win32, HRESULT y NTSTATUS
Antes de buscar 0x80004005, descompóngalo. Explicamos la estructura de tres capas Win32/HRESULT/NTSTATUS, el patrón 0x8007xxxx y cómo inv...
Usar WMI/CIM desde C# y PowerShell ── Guía práctica de obtención de información de hardware, monitorización de procesos y consultas remotas
WMI/CIM es la solución estándar para leer el número de serie, monitorizar el disco y detectar procesos. Cmdlets CIM, migración desde Get-...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Cómo debo elegir entre canalizaciones con nombre y TCP (un socket localhost)?
- Para la comunicación entre procesos en la misma máquina, la canalización con nombre es la primera candidata. El motivo es el modelo de seguridad. Una canalización puede controlar «quién puede conectarse» a nivel del sistema operativo con un descriptor de seguridad de Windows (ACL), y el servidor puede inspeccionar y tomar prestada la cuenta de Windows del interlocutor con ImpersonateNamedPipeClient. Eso contrasta con un puerto TCP localhost, al que cualquiera puede conectarse, de modo que hay que establecer quién es el interlocutor con una autenticación propia. Por otro lado, las opciones basadas en TCP son ventajosas cuando es probable que más adelante haya comunicación remota, cuando también se habla con procesos de otros sistemas operativos, o cuando se quiere reutilizar un protocolo ya existente como gRPC. Este criterio también está desarrollado en el artículo sobre cómo elegir la comunicación entre procesos en Windows.
- ¿Debo usar el modo byte o el modo mensaje?
- Si se quiere tratar «una escritura = una unidad de significado», el modo mensaje (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) resulta cómodo. El receptor puede leer en las unidades que escribió el emisor, así que no hay que gestionar los límites por cuenta propia. El modo byte es un «flujo de bytes ininterrumpido» como TCP, y hay que diseñar el framing uno mismo —por ejemplo, un prefijo de longitud—. Si se transporta un protocolo que ya tiene framing (por ejemplo, una forma serializada con prefijo de longitud), el modo byte es válido. Un aviso: incluso en modo mensaje, si el búfer de recepción es más pequeño que el mensaje se produce una lectura partida (ERROR_MORE_DATA), así que hay que tratarla. Además, el modo de lectura es un ajuste por identificador, y CreateNamedPipe lo fija solo en el lado servidor. El cliente debe indicar PIPE_READMODE_MESSAGE con SetNamedPipeHandleState después de CreateFile. En .NET el servidor indica PipeTransmissionMode.Message y el cliente asigna NamedPipeClientStream.ReadMode a Message después de conectar.
- ¿Cómo construyo un servidor que hable con varios clientes a la vez?
- Una canalización con nombre puede crear varias instancias bajo el mismo nombre, y una instancia atiende a un cliente. Hay dos formas. Una es un diseño síncrono que asigna un hilo por cliente; la implementación es directa, pero consume un hilo por cliente. La otra es usar E/S asíncrona con FILE_FLAG_OVERLAPPED y que un número reducido de hilos gestione ConnectNamedPipe, ReadFile y WriteFile de todas las instancias; el ejemplo oficial de Microsoft también muestra una implementación que procesa varias instancias en un solo hilo. En .NET se puede escribir la forma asíncrona con casi la misma naturalidad que la síncrona, usando NamedPipeServerStream.WaitForConnectionAsync y async/await. Salvo que haya un motivo concreto, la forma asíncrona de .NET es la que recomiendo para implementaciones nuevas.
- ¿Cuál es el mínimo que debo hacer para la seguridad de una canalización con nombre?
- Cuatro puntos. Primero, si no se necesitan conexiones remotas, indicar PIPE_REJECT_REMOTE_CLIENTS y rechazar de forma explícita las conexiones por la red. Segundo, fijar un ACL adecuado con SECURITY_ATTRIBUTES y restringir los usuarios y grupos que pueden conectarse (el ACL predeterminado es demasiado laxo para algunos usos). Tercero, indicar FILE_FLAG_FIRST_PIPE_INSTANCE al crear la primera instancia, para detectar el «secuestro de nombre» en el que se crea antes una canalización del mismo nombre (no ponga esta marca en la segunda instancia ni en las posteriores). Cuarto, un cliente que solo quiere que el servidor lo identifique debe indicar SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION en CreateFile, para que un servidor falso no pueda tomar prestados (suplantar) sus privilegios. En un diseño de broker en el que el servidor realiza un acceso real con los privilegios del cliente, la suplantación tiene que estar permitida, así que se usa o no esta restricción según el diseño deje o no que el servidor tome prestados los privilegios.
- ¿Hay precauciones al usar ImpersonateNamedPipeClient?
- La más importante es comprobar el valor de retorno. Si se continúa después de que la suplantación falle, las operaciones siguientes se ejecutan con los privilegios propios del proceso servidor (a menudo altos), y se dejan pasar operaciones que no deberían haberse permitido al cliente. La documentación oficial también indica de forma explícita que, si falla, no se debe ejecutar la petición del cliente. También hay que llamarla solo después de haber leído algo —la suplantación se realiza en el contexto de «el último mensaje leído de la canalización»— y volver de forma fiable al contexto original con RevertToSelf cuando el trabajo termina. La maquinaria en torno a la suplantación (tokens, niveles de suplantación, SeImpersonatePrivilege) se trata en detalle en el artículo sobre tokens de suplantación.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.