Historial de revisiones (primera versión, publicada el 22 Aug 2026)
- Primera publicación
Citar este artículo(DOI (archivo registrado): 10.5281/zenodo.22176765)
Los DOI siguientes remiten a versiones ya archivadas y pueden diferir del texto actual. Para citar el texto actual, utilice la URL de esta página.
Go Komura (2026). Canalizaciones con nombre en la práctica — El IPC estándar de Windows, del diseño a la seguridad. KomuraSoft LLC. https://comcomponent.com/es/blog/windows-named-pipes-practical-guide/
- DOI (archivo registrado)
- 10.5281/zenodo.22176765
- DOI (última versión registrada)
- 10.5281/zenodo.22176766
«Quiero enviar comandos desde una pantalla de configuración a un servicio residente.» «Quiero aislar en un proceso distinto solo el trabajo que necesita privilegios de administrador.» Cuando se construye este tipo de comunicación entre procesos (IPC) en Windows, lo primero que conviene considerar es una canalización con nombre.
La razón de que sea la primera candidata para la comunicación dentro del mismo PC no es solo la facilidad de lectura y escritura. La gran ventaja es que se puede restringir quién se conecta con los ACL de Windows y, cuando hace falta, comprobar la cuenta de Windows del interlocutor y trabajar con sus privilegios. Crear una canalización no la hace segura por sí sola; hay que configurar la conexión y los permisos.123
Este artículo organiza el diseño en el orden forma de la conexión, límites de los mensajes, estructura del servidor, seguridad y comportamiento ante un fallo. Se dirige a desarrolladores que escriben aplicaciones empresariales y servicios en Windows. Toma la «primera candidata para la IPC en la misma máquina» del artículo sobre la tabla de decisión de la comunicación entre procesos y la profundiza hasta las decisiones de implementación.
1. La conclusión primero: cinco decisiones de diseño
Antes de elegir una API, decida el interlocutor de la comunicación, la unidad de datos, las conexiones simultáneas, los privilegios y el tratamiento de los fallos.
| Decisión | Criterio básico | Dónde leer más |
|---|---|---|
| Con quién comunicar | Para procesos de Windows en el mismo PC, una canalización con nombre es la primera candidata. Si importan el despliegue remoto, otros sistemas operativos o reutilizar un protocolo existente, considere una opción basada en TCP | Capítulo 2 |
| Qué cuenta como una unidad | Si una escritura se trata como una unidad, modo mensaje. Si ya hay framing, modo byte | Capítulo 3 |
| Cómo aceptar varios clientes | Preparar varias instancias del mismo nombre. En una implementación nueva de .NET, la E/S asíncrona y async/await son la opción natural | Capítulo 4 |
| Quién puede conectarse y tomar prestados privilegios | Diseñar como un conjunto el rechazo remoto, el ACL, la garantía de la primera instancia y el nivel de suplantación del cliente | Capítulos 5 y 6 |
| Qué hacer cuando falla la comunicación | Incluir en el protocolo esperas de arranque, reconexión, un límite de tamaño de mensaje y confirmación de respuesta | Capítulo 7 |
Con una canalización con nombre, el control de conexiones mediante ACL y la suplantación del cliente están disponibles como mecanismos de Windows. Con TCP localhost hay que diseñar una autenticación aparte para establecer quién es el interlocutor. Por otro lado, si más adelante se quiere ampliar a comunicación remota, hablar con otros sistemas operativos o usar activos existentes como gRPC, una opción basada en TCP es ventajosa.23
Lo más importante es no ejecutar una petición cuya suplantación haya fallado. Si se ignora el fallo, el procesamiento sigue con los privilegios propios del servidor, no con los del cliente. Si está construyendo un servicio privilegiado, lea los capítulos 5 y 6, no solo los ejemplos de código.4
2. Cómo funcionan las conexiones: un nombre compartido, un canal por cliente
2.1 Separar los roles de crear, conectar y leer/escribir
Una canalización con nombre es un canal unidireccional o bidireccional identificado por un nombre como \\.\pipe\MyCompany.MyApp.Control. El servidor y el cliente empiezan con API distintas.1
| Etapa | Lado servidor | Lado cliente |
|---|---|---|
| Preparar el canal | Crear una instancia con CreateNamedPipe |
Usar el nombre de canalización que preparó el servidor |
| Conectar | Esperar a un cliente con ConnectNamedPipe |
Abrir el mismo nombre con CreateFile |
| Intercambiar datos | Leer y escribir con ReadFile / WriteFile |
Leer y escribir con ReadFile / WriteFile |
Tras conectar, ambos lados la tratan de la misma forma que la E/S de archivos. Más allá de indicar el nombre, comprender las ideas siguientes de instancia y dirección facilita entender las conexiones múltiples y los errores de conexión.
2.2 Una instancia atiende a un cliente
Se pueden crear varias canalizaciones con el mismo nombre, y una instancia se convierte en el canal con un cliente. Compartir un nombre no significa que todos los clientes compartan un solo canal. El primer CreateNamedPipe indica el número máximo de instancias. También está disponible PIPE_UNLIMITED_INSTANCES como límite superior.15
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 conexiones 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 1: Un ejemplo de canalización bidireccional. Al preparar varias instancias del mismo nombre, el servidor se comunica uno a uno con cada uno de varios clientes.
2.3 La «dirección» se nombra desde el punto de vista del servidor
La especificación de acceso del cliente debe coincidir con la dirección de la canalización que creó el servidor. Si no coinciden, CreateFile falla.6
| Dirección que crea el servidor | Comportamiento del servidor | Acceso que indica el cliente |
|---|---|---|
| Bidireccional | Lee y escribe | Se puede abrir para lectura, para escritura o para ambas |
| De salida | Solo escribe | Abrir en solo lectura |
| De entrada | Solo lee | Abrir en solo escritura |
Si todas las instancias están en uso, el CreateFile del cliente devuelve ERROR_PIPE_BUSY. Espere a una instancia libre con WaitNamedPipe y vuelva a llamar a CreateFile. Es un fallo distinto del de que el servidor aún no haya creado la canalización, así que la sección 7.1 lo trata junto con las carreras de orden de arranque.6
2.4 Incluso para uso local, decida cómo se tratan las conexiones remotas
Una canalización con nombre también se puede usar para conexiones remotas a través de SMB, en la forma \\server\pipe\nombre. En un diseño moderno, sin embargo, casi no hay motivo para usar este camino de forma activa, y en la IPC local lo importante es no dejar abierto un camino que no se usa. Rechácelo de forma explícita con PIPE_REJECT_REMOTE_CLIENTS en la sección 5.1.17
3. Elegir un modo: decidir cuánto es una unidad
3.1 Modo mensaje para petición/respuesta, modo byte si ya hay fronteras
La diferencia entre los modos de transferencia es si la canalización conserva los límites de las escrituras.5
| Modo | Lo que ve el receptor | Adecuado para |
|---|---|---|
Modo byte (PIPE_TYPE_BYTE) |
Un flujo de bytes ininterrumpido, igual que TCP | Datos que llevan su propio framing, como un prefijo de longitud, o transferencias en flujo |
Modo mensaje (PIPE_TYPE_MESSAGE con lectura en modo mensaje) |
Unidades en las que una escritura es un mensaje | Peticiones y respuestas de una en una |
flowchart TB
accTitle: Diferencia entre el modo byte y el modo mensaje
accDescr: En modo byte tres escrituras se convierten en un flujo de bytes ininterrumpido que el receptor tiene que partir por su cuenta, mientras que en modo mensaje se conserva la unidad de cada escritura y llega al receptor tal cual
bw["Modo byte: escribir AAA, BB, CCCC"] --> br["Recepción como el flujo de bytes AAABBCCCC"]
br --> bf["Límites (framing) diseñados por cuenta propia"]
mw["Modo mensaje: las mismas tres escrituras"] --> mr["Recepción como tres unidades: AAA, BB, CCCC"]
mr --> mf["Se conserva la unidad de cada escritura"]
Figura 2: En modo byte el receptor gestiona los límites. El modo mensaje puede conservar la unidad de cada escritura, pero una sola lectura no recibe necesariamente el mensaje entero.
Si aún no tiene fronteras propias y quiere tratar una escritura como una petición, el modo mensaje es cómodo. Si ya usa algo como un formato serializado con prefijo de longitud, el modo byte es válido. En .NET, PipeTransmissionMode.Message es la especificación del tipo mensaje.8
Para una canalización bidireccional de tipo mensaje también existe TransactNamedPipe, que envía una petición y recibe la respuesta en una sola llamada.9
3.2 El tipo mensaje y el modo de lectura son ajustes distintos
El modo de lectura se fija por identificador. Indicar PIPE_READMODE_MESSAGE en CreateNamedPipe es un ajuste del lado servidor. Un identificador que el cliente abrió con CreateFile está por defecto en modo de lectura de bytes.6
| Implementación | Lado servidor | Lado cliente |
|---|---|---|
| Win32 | Indicar PIPE_TYPE_MESSAGE y PIPE_READMODE_MESSAGE |
Tras conectar, indicar PIPE_READMODE_MESSAGE con SetNamedPipeHandleState |
| .NET | Indicar PipeTransmissionMode.Message |
Tras conectar, asignar NamedPipeClientStream.ReadMode a Message |
El punto es no dar por hecho que hacer solo el servidor de tipo mensaje permite al cliente leer en las mismas unidades.
3.3 Incluso en modo mensaje, hay que leer el resto
Conservar los límites del mensaje y que el mensaje entero quepa en el búfer de recepción son dos cosas distintas. Si el búfer es pequeño, ReadFile devuelve ERROR_MORE_DATA y el mensaje se parte. Hace falta un bucle que conserve la parte ya recibida, lea el resto y los concatene.56
| Resultado de lectura | Tratamiento |
|---|---|
| Éxito | Procesar el mensaje como completo |
ERROR_MORE_DATA |
Aún queda más, así que leer el resto y concatenar |
| Cualquier otro error | No seguir procesando la petición; tratarlo como un fallo de comunicación, como una desconexión |
Si las peticiones pequeñas pasan pero solo se rompen las grandes, compruebe esta lógica de leer el resto. Además, concatenar sin límite no es aceptable. Decida un tamaño máximo para un mensaje como parte del protocolo y adopte la política de desconectar cuando se supere el límite. Así se evita el desperdicio de memoria causado por mensajes enormes.
4. Estructura del servidor: separar la aceptación del tratamiento de clientes
4.1 Comparar el diseño de hilos síncronos y el diseño overlapped
La operación básica del servidor es el ciclo crear una instancia, esperar una conexión, leer y escribir, desconectar y pasar a la siguiente. Para aceptar varios clientes a la vez, ejecute este flujo en varias instancias.
| Diseño | Mecanismo | Ventajas y precauciones |
|---|---|---|
| Diseño de hilos síncronos | Asignar un hilo por instancia y procesar con E/S síncrona | El código es directo. Sin embargo, consume un hilo por cliente y hace falta una forma de salir de la E/S bloqueante al detenerse |
| Diseño overlapped (asíncrono) | Crear con FILE_FLAG_OVERLAPPED y emitir conexión, lectura y escritura de forma asíncrona |
Un número reducido de hilos puede tratar las finalizaciones de varias instancias |
El ejemplo oficial de Microsoft pone el evento de cada instancia en una matriz, espera con WaitForMultipleObjects y procesa varias conexiones en un solo hilo. Desacopla el número de clientes del número de hilos. A mayor escala, también se puede enlazar IOCP o la E/S del grupo de hilos.10
Para cómo funciona la propia E/S asíncrona, véase el artículo sobre E/S síncrona y asíncrona.
4.2 En una implementación nueva de .NET, async/await es la opción natural
En .NET se pueden usar WaitForConnectionAsync / ReadAsync / WriteAsync de NamedPipeServerStream con async/await. Salvo que haya un motivo concreto, recomiendo este diseño asíncrono para implementaciones nuevas. Cuando el bucle de aceptación recibe una conexión, la entrega al tratamiento por cliente y vuelve a aceptar en la instancia siguiente.8
Lo siguiente es el esqueleto de ese bucle de aceptación. Como ejemplo de uso entre procesos del mismo usuario, indica PipeOptions.Asynchronous y PipeOptions.CurrentUserOnly. En Windows, CurrentUserOnly restringe las conexiones al mismo usuario y al mismo nivel de elevación. No es un ejemplo de conectar un servicio y una interfaz con cuentas distintas. Ese caso necesita el diseño de ACL de la sección 5.2.11
// 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 usted mismo al salir antes de una conexión
throw;
}
_ = HandleClientAsync(server, token); // tras conectar, la propiedad pasa al controlador
}
Si se produce una excepción antes de una conexión, el lado de aceptación libera el flujo; después de una conexión, HandleClientAsync asume la propiedad del flujo. Esa propiedad incluye no solo la lectura y la escritura, sino también liberarlo al final.
Este código es un esqueleto que omite el protocolo de comunicación y el cuerpo del controlador. En producción, gestione las excepciones y la finalización de las tareas desprendidas, y combínelo con la lectura del resto y el límite de tamaño del capítulo 3, la seguridad de los capítulos 5 y 6, y el tratamiento de desconexiones y la confirmación de respuesta del capítulo 7. CurrentUserOnly es una opción cómoda para limitar a quién se conecta sin escribir un ACL uno mismo, pero por sí sola no completa un diseño para un servicio privilegiado.
5. Seguridad: tres puntos del lado servidor y uno del lado cliente
Las ventajas de seguridad de las canalizaciones con nombre solo se obtienen cuando se configuran correctamente. En particular, en el diseño de broker de «un servicio con privilegios de administrador más una aplicación de interfaz con pocos privilegios», la canalización es la frontera de privilegios.
5.1 Lado servidor: rechazar las conexiones remotas innecesarias
Si usa la canalización como IPC local, indique PIPE_REJECT_REMOTE_CLIENTS en CreateNamedPipe. Los clientes remotos se rechazan automáticamente, lo que cierra el camino en el que una canalización pensada para aplicaciones del mismo PC también se puede abrir desde la red.7
5.2 Lado servidor: restringir conectados y derechos con un ACL
Pase un descriptor de seguridad en SECURITY_ATTRIBUTES y deje explícitos qué usuarios y grupos pueden conectarse. El ACL predeterminado no es necesariamente lo bastante estricto para su uso.2
Lo que es fácil de pasar por alto aquí es el permiso de escritura que se concede a los clientes. Si el ACL concede escritura genérica (GENERIC_WRITE / FILE_GENERIC_WRITE), incluye el derecho equivalente a FILE_CREATE_PIPE_INSTANCE. Un cliente autorizado puede entonces crear él mismo una instancia de servidor del mismo nombre e interceptar las conexiones posteriores.2
Conceda la lectura y la escritura como los derechos individuales necesarios, y no entregue a los clientes el derecho de crear instancias. El diseño de ACL cubre no solo «a quién se permite» sino también «qué se permite».
5.3 Lado servidor: comprobar que la primera instancia no se ha tomado por delante
Si un proceso malicioso crea primero una canalización del mismo nombre, los clientes se conectan a ese servidor falso. El servidor indica FILE_FLAG_FIRST_PIPE_INSTANCE solo al crear la primera instancia, y así garantiza que es el primero. Si eso falla, sospeche que el nombre se tomó por delante y deténgase.5
Esta marca es solo para la primera instancia, la que reserva el nombre. Ponerla también en la segunda instancia y en las posteriores hace fallar la creación. En un bucle de aceptación para varios clientes, no repita la misma especificación en cada creación.5
Tenga en cuenta, no obstante, que este es un mecanismo para que el servidor legítimo detecte una anomalía al arrancar, no una autenticación del destino de conexión por el cliente. Si el servicio legítimo está ausente, un atacante aún tiene margen para crear primero una canalización del mismo nombre y esperar.
5.4 Lado cliente: decidir hasta dónde puede el servidor tomar prestados los privilegios
Como defensa frente a un servidor falso, el cliente mantiene el nivel de suplantación en el mínimo necesario. Si el diseño solo permite la verificación de identidad, indique SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION en CreateFile. El servidor puede entonces comprobar la cuenta, pero ya no puede tomar prestados sus privilegios para realizar un acceso real.3
| Lo que se quiere que haga el servidor | Política del lado cliente |
|---|---|
| Solo verificar la identidad del conectado | Restringir al nivel de identificación (SECURITY_IDENTIFICATION) |
| Realizar un acceso real, como abrir archivos con los privilegios del cliente | Debe permitirse el nivel de suplantación (SECURITY_IMPERSONATION). Acompañarlo de verificar que se está conectado al servidor legítimo |
En el nivel de identificación no es posible el procesamiento de broker que realiza un acceso real con los privilegios del cliente. A la inversa, permitir la suplantación sin verificar el destino de conexión corre el riesgo de que un servidor falso tome prestados los privilegios.
El principio es permitir la suplantación necesaria solo cuando se puede confirmar el destino de conexión legítimo, mediante un arranque de servicio garantizado o autenticación mutua tras conectar. No dé por hecho que la comprobación del lado cliente es innecesaria porque exista la detección de toma anticipada de la sección 5.3.
6. Usar la suplantación: no ejecutar una petición cuya suplantación falló
6.1 Suplantar después de leer la petición, no solo después de conectar
La API para que el servidor verifique la identidad del cliente y procese con sus privilegios es ImpersonateNamedPipeClient. Su objeto es el contexto de seguridad del emisor del último mensaje leído de esa canalización. Lea primero la petición y luego llámela.4
La suplantación se aplica al hilo que llama. Si en ese estado abre un archivo, si el acceso está permitido se juzga según los privilegios del cliente. Incluso para un servicio privilegiado, este es el mecanismo para «ejecutar la operación pedida con los privilegios de quien la pidió».3
6.2 Hacer de la lectura, la confirmación de éxito y la reversión un solo conjunto
El orden requerido es leer la petición, intentar la suplantación, confirmar el éxito, operar con los privilegios del cliente y volver al contexto original.
sequenceDiagram
accTitle: Flujo del tratamiento de una petición que usa suplantación
accDescr: El servidor lee una petición de la canalización, confirma que ImpersonateNamedPipeClient tuvo éxito, luego 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: Volver al contexto original con RevertToSelf
S->>C: Responder con el resultado
Figura 3: Si la suplantación falla, no ejecute la petición. Incluso si tiene éxito, la secuencia no termina hasta que RevertToSelf restaura el contexto original después del trabajo.
No pase a procesar la petición sin comprobar el valor de retorno de ImpersonateNamedPipeClient. Si falla, el hilo no ha pasado a los privilegios del cliente y se usan los privilegios propios del proceso servidor. Si el servidor se ejecuta bajo una cuenta privilegiada, deja pasar operaciones que el cliente no tiene permitidas. La documentación de Microsoft también indica de forma explícita que, si falla, no se debe ejecutar la petición del cliente.4
Cuando el trabajo termina, vuelva de forma fiable al contexto original con RevertToSelf. Tenga tanto la comprobación de éxito como la limpieza. Detalles como tokens, niveles de suplantación y SeImpersonatePrivilege se cubren en el artículo sobre tokens de suplantación de Windows.
7. Escollos prácticos: diseñar asumiendo que la comunicación se cortará
7.1 Tratar la espera de arranque y la espera de una instancia libre como fallos distintos
Cuando no se puede conectar, distinga entre que el servidor aún no haya creado la canalización y que todas las instancias creadas estén en uso.69
| Estado | Acción del cliente |
|---|---|
| La canalización no existe | Contar con que el servidor aún está arrancando; esperar un poco y reintentar |
ERROR_PIPE_BUSY |
Esperar a una instancia libre con WaitNamedPipe y reintentar CreateFile |
| Conectado | Pasar a la comunicación. Si hace falta, fijar también el modo de lectura de la sección 3.2 |
flowchart TB
accTitle: Flujo de reintento de conexión del cliente
accDescr: Abrir la canalización con CreateFile; si no existe, esperar un poco y reintentar; si todas las instancias están en uso y se devuelve ERROR_PIPE_BUSY, esperar a una instancia libre con WaitNamedPipe y luego reintentar; si tiene éxito, empezar a comunicar
cf["Abrir con CreateFile"] --> ok{"¿Resultado?"}
ok -->|"Éxito"| go["Empezar a comunicar"]
ok -->|"La canalización no existe"| wait1["Esperar un poco (servidor no arrancado)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["Esperar a una instancia libre con WaitNamedPipe"]
wait1 --> cf
wnp --> cf
Figura 4: «No existe» y «llena» son estados distintos. En ambos casos, espere según el estado y luego reintente la conexión.
En el lado servidor, el principio es empezar a esperar con ConnectNamedPipe antes de que arranque el cliente. Aun así pueden ocurrir carreras de orden de arranque, así que incorpore reintentos también en el lado cliente.9
7.2 Convertir la desconexión en una ruta de procesamiento normal, no en una emergencia
Cuando el proceso interlocutor termina, las lecturas y escrituras fallan con ERROR_BROKEN_PIPE y similares. Trátelo como el día a día de la comunicación.
Cuando el servidor detecta una desconexión, desprende la instancia con DisconnectNamedPipe y se prepara para la siguiente conexión. El cliente se reconecta. La idea de «reconexión idempotente», que no corrompe el estado por muchas veces que se repita, explicada en el artículo sobre reanudar de la suspensión, también es eficaz aquí.
7.3 Los mensajes grandes necesitan tanto «leer el resto» como «un límite»
El tratamiento de ERROR_MORE_DATA de la sección 3.3 es la lógica para leer un mensaje a trozos. El tamaño máximo de mensaje, en cambio, es una regla para limitar la cantidad de datos que se acepta. No confunda las dos.
Si un interlocutor malicioso o con un error envía un mensaje enorme, una implementación que lee el resto sin límite desperdicia memoria. Adopte la política de definir un límite en el protocolo y desconectar cuando se supere.
7.4 Distinguir el éxito de una escritura de que el interlocutor haya terminado de procesar
El éxito de WriteFile no significa que la aplicación del interlocutor haya procesado los datos. En las operaciones en las que hay que saber que realmente se ejecutaron, confírmelo con un mensaje de respuesta.
Además, para que cada respuesta se pueda emparejar con la petición a la que responde, incluya en el protocolo la relación entre peticiones y respuestas. Separar «se envió» de «el procesamiento terminó» es lo que lleva a la confirmación de finalización en sentido empresarial.
8. Resumen: decidir por separado el canal, los datos y los privilegios
Una canalización con nombre combina la facilidad de uso de la E/S de archivos con el modelo de seguridad de Windows de ACL y suplantación, y es la primera candidata para la IPC en la misma máquina. Crear un canal, sin embargo, es un asunto distinto de crear un protocolo seguro y difícil de romper.
En el diseño de la conexión y de los datos, parta de que una instancia atiende a un cliente. Elija el modo mensaje para petición/respuesta y el modo byte si ya hay framing, y fije también el modo de lectura de mensaje en el lado cliente. También hacen falta el tratamiento de lecturas partidas y un tamaño máximo. En una implementación nueva de .NET que acepta varias conexiones, la E/S asíncrona y async/await son la opción natural.
En el diseño de los privilegios, trate como un conjunto el rechazo remoto, un ACL explícito, la garantía de la primera instancia y la minimización del nivel de suplantación del lado cliente. Si usa suplantación, llámela después de leer la petición, no ejecute si falla y revierta con RevertToSelf después del trabajo.
Por último, escriba el orden de arranque, la desconexión, el límite de tamaño de mensaje y la confirmación de respuesta como su propio protocolo. El punto práctico es decidir, antes de empezar a implementar, una forma que pueda recuperarse cuando se corte la comunicación sin exceder los privilegios del interlocutor, en lugar de asumir «está en el mismo PC, así que no fallará».
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
KomuraSoft LLC se ocupa del diseño e implementación de sistemas que implican comunicación entre procesos, como separar un servicio de su aplicación de interfaz y aislar los privilegios de administrador; de sustituir IPC existente (memoria compartida, sockets propios, COM, etc.) por canalizaciones con nombre; y de revisiones de seguridad de la comunicación por canalización de un servicio privilegiado. Puede consultarnos incluso si solo quiere hablar de un diseño de protocolo.
- 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 de canalización; sobre que todas las instancias comparten el mismo nombre a la vez que tienen búferes e identificadores independientes; y sobre el uso desde procesos locales y remotos. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Security and Access Rights. Sobre la composición de los derechos de acceso de las canalizaciones con nombre; sobre que GENERIC_WRITE incluye FILE_CREATE_PIPE_INSTANCE, de modo que conceder escritura genérica a un cliente también permite crear una instancia de servidor; y sobre que la lectura y la escritura de datos deben concederse como derechos de acceso individuales. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Impersonating a Named Pipe Client. Sobre que la suplantación permite al hilo del servidor operar 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 CreateFile (SECURITY_IDENTIFICATION solo permite identificación). ↩ ↩2 ↩3 ↩4
-
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 completar; y sobre que continuar después de que falle la suplantación provoca la ejecución en el contexto propio (privilegiado) del proceso servidor, de modo que siempre hay que comprobar el valor de retorno y, si falla, no ejecutar la petición del cliente. ↩ ↩2 ↩3
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sobre la dirección de la canalización (de entrada, de 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 la primera instancia mediante FILE_FLAG_FIRST_PIPE_INSTANCE, y el tiempo de espera predeterminado que usa WaitNamedPipe. ↩ ↩2 ↩3 ↩4 ↩5
-
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 y esperar a una libre con WaitNamedPipe; y sobre que el identificador abierto está por defecto en lectura de bytes, bloqueante y no overlapped, y SetNamedPipeHandleState puede cambiarlo a modo de lectura de mensaje. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sobre los dos modos de cliente remoto, PIPE_ACCEPT_REMOTE_CLIENTS (aceptar conexiones remotas y comprobarlas contra el descriptor de seguridad) y PIPE_REJECT_REMOTE_CLIENTS (rechazar automáticamente las conexiones de clientes remotos). ↩ ↩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 tratamiento de varios clientes con métodos asíncronos. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. Sobre operaciones overlapped mediante ReadFileEx / WriteFileEx, una lectura no consumidora mediante PeekNamedPipe, TransactNamedPipe enviando una petición y recibiendo la respuesta en una llamada en una canalización bidireccional de tipo mensaje, y una lectura bloqueante antes de que arranque el cliente pudiendo causar una carrera. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. Sobre el ejemplo oficial de un servidor de un solo hilo que trata conexiones simultáneas con varios clientes mediante operaciones overlapped. Sobre la estructura que espera 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 E/S pendientes con GetOverlappedResult. ↩
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Sobre habilitar 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). ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Qué queda después de que muere el padre — mantener los procesos hijos en un Job Object
Por qué los ayudantes del SDK sobreviven a una IU terminada y retienen la cámara o el puerto COM. Cómo diseñar la vida de los procesos hi...
Por qué se rompen los argumentos — Las reglas de los argumentos de línea de comandos de Windows
Windows pasa a CreateProcess una sola cadena que el receptor divide. Cubre las reglas de CommandLineToArgvW, el CRT y .NET, ArgumentList ...
API del grupo de hilos de Win32 — Concurrencia sin crear hilos, con CreateThreadpoolWork
¿Está multiplicando las llamadas a CreateThread en el código nativo? Este artículo explica, a partir de fuentes primarias, la API del gru...
DllMain y el bloqueo del cargador — La razón real de que le digan «no haga nada en la inicialización de la DLL»
Por qué no debe llamar a LoadLibrary ni sincronizar con otros hilos desde DllMain. A partir de fuentes primarias, este artículo explica e...
Despertares espurios — por qué una variable de condición despierta «sin notificación» y cómo esperar correctamente en Windows
El wait de una variable de condición puede volver sin notificación (despertar espurio). El artículo explica, a partir de la implementació...
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 controla 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, una opción basada en TCP es ventajosa 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 usar un protocolo ya existente como gRPC. Este criterio también está desarrollado en el artículo sobre la tabla de decisión de la comunicación entre procesos.
- ¿Debo usar el modo byte o el modo mensaje?
- Si se quiere tratar una escritura como 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, WaitForConnectionAsync de NamedPipeServerStream y async/await permiten escribir el diseño asíncrono con casi la misma naturalidad que el síncrono. 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 distantes, 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 del ú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.