GNU/Linux >> Tutoriales Linux >  >> Linux

¿En qué se diferencian SO_REUSEADDR y SO_REUSEPORT?

Bienvenido al maravilloso mundo de la portabilidad... o más bien la falta de ella. Antes de que comencemos a analizar estas dos opciones en detalle y demos una mirada más profunda a cómo las manejan los diferentes sistemas operativos, se debe tener en cuenta que la implementación de socket BSD es la madre de todas las implementaciones de socket. Básicamente, todos los demás sistemas copiaron la implementación del socket BSD en algún momento (o al menos sus interfaces) y luego comenzaron a evolucionar por su cuenta. Por supuesto, la implementación del socket BSD también evolucionó al mismo tiempo y, por lo tanto, los sistemas que lo copiaron más tarde obtuvieron características que faltaban en los sistemas que lo copiaron antes. Comprender la implementación del socket BSD es la clave para comprender todas las demás implementaciones de socket, por lo que debe leer al respecto incluso si no desea escribir código para un sistema BSD.

Hay un par de conceptos básicos que debe saber antes de analizar estas dos opciones. Una conexión TCP/UDP se identifica mediante una tupla de cinco valores:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

Cualquier combinación única de estos valores identifica una conexión. Como resultado, dos conexiones no pueden tener los mismos cinco valores; de lo contrario, el sistema ya no podría distinguir estas conexiones.

El protocolo de un socket se establece cuando se crea un socket con el socket() función. La dirección de origen y el puerto se establecen con el bind() función. La dirección de destino y el puerto se configuran con el connect() función. Dado que UDP es un protocolo sin conexión, los sockets UDP se pueden usar sin conectarlos. Sin embargo, está permitido conectarlos y, en algunos casos, es muy ventajoso para su código y el diseño general de la aplicación. En el modo sin conexión, los sockets UDP que no estaban vinculados explícitamente cuando se envían datos a través de ellos por primera vez, por lo general, el sistema los vincula automáticamente, ya que un socket UDP no vinculado no puede recibir ningún dato (de respuesta). Lo mismo es cierto para un socket TCP no vinculado, se vincula automáticamente antes de que se conecte.

Si vincula explícitamente un socket, es posible vincularlo al puerto 0 , que significa "cualquier puerto". Dado que un socket realmente no puede vincularse a todos los puertos existentes, el sistema tendrá que elegir un puerto específico en ese caso (generalmente de un rango de puertos de origen predefinido y específico del sistema operativo). Existe un comodín similar para la dirección de origen, que puede ser "cualquier dirección" (0.0.0.0 en caso de IPv4 y :: en el caso de IPv6). A diferencia del caso de los puertos, un socket realmente puede vincularse a "cualquier dirección", lo que significa "todas las direcciones IP de origen de todas las interfaces locales". Si el socket se conecta más tarde, el sistema debe elegir una dirección IP de origen específica, ya que un socket no se puede conectar y al mismo tiempo estar vinculado a ninguna dirección IP local. Según la dirección de destino y el contenido de la tabla de enrutamiento, el sistema elegirá una dirección de origen adecuada y reemplazará el enlace "cualquiera" con un enlace a la dirección IP de origen elegida.

De forma predeterminada, no se pueden vincular dos sockets a la misma combinación de dirección de origen y puerto de origen. Siempre que el puerto de origen sea diferente, la dirección de origen es irrelevante. Enlace socketA a ipA:portA y socketB a ipB:portB siempre es posible si ipA != ipB es cierto, incluso cuando portA == portB . P.ej. socketA pertenece a un programa de servidor FTP y está vinculado a 192.168.0.1:21 y socketB pertenece a otro programa de servidor FTP y está vinculado a 10.0.0.1:21 , ambos enlaces se realizarán correctamente. Sin embargo, tenga en cuenta que un socket puede estar vinculado localmente a "cualquier dirección". Si un socket está vinculado a 0.0.0.0:21 , está vinculado a todas las direcciones locales existentes al mismo tiempo y, en ese caso, ningún otro socket puede vincularse al puerto 21 , independientemente de la dirección IP específica a la que intente vincularse, como 0.0.0.0 entra en conflicto con todas las direcciones IP locales existentes.

Todo lo dicho hasta ahora es prácticamente igual para todos los principales sistemas operativos. Las cosas comienzan a volverse específicas del sistema operativo cuando entra en juego la reutilización de direcciones. Comenzamos con BSD, ya que como dije anteriormente, es la madre de todas las implementaciones de socket.

BSD

SO_REUSEADDR

Si SO_REUSEADDR está habilitado en un socket antes de vincularlo, el socket se puede vincular con éxito a menos que haya un conflicto con otro socket vinculado a exactamente la misma combinación de dirección de origen y puerto. Ahora puede que se pregunte en qué se diferencia eso de antes. La palabra clave es "exactamente". SO_REUSEADDR cambia principalmente la forma en que se tratan las direcciones comodín ("cualquier dirección IP") al buscar conflictos.

Sin SO_REUSEADDR , enlazando socketA a 0.0.0.0:21 y luego vincular socketB a 192.168.0.1:21 fallará (con error EADDRINUSE ), dado que 0.0.0.0 significa "cualquier dirección IP local", por lo tanto, todas las direcciones IP locales se consideran en uso por este socket y esto incluye 192.168.0.1 , también. Con SO_REUSEADDR tendrá éxito, ya que 0.0.0.0 y 192.168.0.1 son no exactamente la misma dirección, uno es un comodín para todas las direcciones locales y el otro es una dirección local muy específica. Tenga en cuenta que la afirmación anterior es verdadera independientemente del orden socketA y socketB están obligados; sin SO_REUSEADDR siempre fallará, con SO_REUSEADDR siempre tendrá éxito.

Para darle una mejor visión general, hagamos una tabla aquí y enumeremos todas las combinaciones posibles:

SO_REUSEADDR       socketA        socketB       Result
---------------------------------------------------------------------
  ON/OFF       192.168.0.1:21   192.168.0.1:21    Error (EADDRINUSE)
  ON/OFF       192.168.0.1:21      10.0.0.1:21    OK
  ON/OFF          10.0.0.1:21   192.168.0.1:21    OK
   OFF             0.0.0.0:21   192.168.1.0:21    Error (EADDRINUSE)
   OFF         192.168.1.0:21       0.0.0.0:21    Error (EADDRINUSE)
   ON              0.0.0.0:21   192.168.1.0:21    OK
   ON          192.168.1.0:21       0.0.0.0:21    OK
  ON/OFF           0.0.0.0:21       0.0.0.0:21    Error (EADDRINUSE)

La tabla anterior asume que socketA ya se ha vinculado con éxito a la dirección proporcionada para socketA , luego socketB se crea, obtiene SO_REUSEADDR establecido o no, y finalmente está vinculado a la dirección dada para socketB . Result es el resultado de la operación de enlace para socketB . Si la primera columna dice ON/OFF , el valor de SO_REUSEADDR es irrelevante para el resultado.

De acuerdo, SO_REUSEADDR tiene un efecto en las direcciones comodín, es bueno saberlo. Sin embargo, ese no es el único efecto que tiene. Hay otro efecto bien conocido que también es la razón por la cual la mayoría de la gente usa SO_REUSEADDR en programas de servidor en primer lugar. Para el otro uso importante de esta opción, debemos profundizar en cómo funciona el protocolo TCP.

Si se cierra un socket TCP, normalmente se realiza un protocolo de enlace de 3 vías; la secuencia se llama FIN-ACK . El problema aquí es que el último ACK de esa secuencia puede haber llegado al otro lado o puede que no haya llegado y solo si lo ha hecho, el otro lado también considera que el socket está completamente cerrado. Para evitar la reutilización de una combinación de dirección+puerto, que aún puede ser considerada abierta por algún par remoto, el sistema no considerará inmediatamente un socket como muerto después de enviar el último ACK pero en su lugar coloque el socket en un estado comúnmente conocido como TIME_WAIT . Puede estar en ese estado durante minutos (configuración dependiente del sistema). En la mayoría de los sistemas, puede sortear ese estado habilitando la persistencia y configurando un tiempo de permanencia de cero1, pero no hay garantía de que esto siempre sea posible, que el sistema siempre cumplirá con esta solicitud, e incluso si el sistema la cumple, esto hace que la socket para ser cerrado por un reinicio (RST ), que no siempre es una gran idea. Para obtener más información sobre el tiempo de permanencia, consulte mi respuesta sobre este tema.

La pregunta es, ¿cómo trata el sistema un socket en el estado TIME_WAIT? ? Si SO_REUSEADDR no está configurado, un socket en el estado TIME_WAIT se considera que todavía está vinculado a la dirección y el puerto de origen y cualquier intento de vincular un nuevo socket a la misma dirección y puerto fallará hasta que el socket se haya cerrado realmente. Así que no espere que pueda volver a vincular la dirección de origen de un socket inmediatamente después de cerrarlo. En la mayoría de los casos esto fallará. Sin embargo, si SO_REUSEADDR está configurado para el socket que está tratando de vincular, otro socket vinculado a la misma dirección y puerto en el estado TIME_WAIT simplemente se ignora, después de todo, ya está "medio muerto", y su socket puede vincularse exactamente a la misma dirección sin ningún problema. En ese caso, no juega ningún papel que el otro socket pueda tener exactamente la misma dirección y puerto. Tenga en cuenta que vincular un socket a exactamente la misma dirección y puerto que un socket moribundo en TIME_WAIT El estado puede tener efectos secundarios inesperados, y generalmente no deseados, en caso de que el otro zócalo todavía esté "en funcionamiento", pero eso está más allá del alcance de esta respuesta y, afortunadamente, esos efectos secundarios son bastante raros en la práctica.

Hay una última cosa que debes saber sobre SO_REUSEADDR . Todo lo escrito anteriormente funcionará siempre que el socket al que desea vincularse tenga habilitada la reutilización de direcciones. No es necesario que el otro socket, el que ya está ligado o está en un TIME_WAIT estado, también tenía esta bandera establecida cuando estaba enlazado. El código que decide si el enlace tendrá éxito o falla solo inspecciona el SO_REUSEADDR indicador del socket alimentado en el bind() llamar, para todos los demás enchufes inspeccionados, esta bandera ni siquiera se mira.

SO_REUTILIZAR

SO_REUSEPORT es lo que la mayoría de la gente esperaría SO_REUSEADDR ser - estar. Básicamente, SO_REUSEPORT le permite vincular un número arbitrario de sockets a exactamente la misma dirección de origen y puerto siempre que todos los sockets enlazados anteriores también tenían SO_REUSEPORT fijados antes de ser atados. Si el primer socket que está vinculado a una dirección y un puerto no tiene SO_REUSEPORT establecido, ningún otro socket puede vincularse exactamente a la misma dirección y puerto, independientemente de si este otro socket tiene SO_REUSEPORT establecido o no, hasta que el primer zócalo suelte su unión nuevamente. A diferencia del caso de SO_REUESADDR el código que maneja SO_REUSEPORT no solo verificará que el socket enlazado actualmente tenga SO_REUSEPORT pero también verificará que el socket con una dirección y un puerto en conflicto tenía SO_REUSEPORT establecido cuando se encuadernó.

SO_REUSEPORT no implica SO_REUSEADDR . Esto significa que si un socket no tiene SO_REUSEPORT establecido cuando estaba enlazado y otro socket tiene SO_REUSEPORT establecido cuando está vinculado exactamente a la misma dirección y puerto, el enlace falla, lo que se esperaba, pero también falla si el otro socket ya está muriendo y está en TIME_WAIT estado. Para poder vincular un socket a las mismas direcciones y puerto que otro socket en TIME_WAIT el estado requiere SO_REUSEADDR para establecerse en ese socket o SO_REUSEPORT debe haberse configurado en ambos enchufes antes de unirlos. Por supuesto, está permitido configurar ambos, SO_REUSEPORT y SO_REUSEADDR , en un enchufe.

No hay mucho más que decir sobre SO_REUSEPORT aparte de eso, se agregó después de SO_REUSEADDR , es por eso que no lo encontrará en muchas implementaciones de socket de otros sistemas, que "bifurcaron" el código BSD antes de que se agregara esta opción, y que no había forma de vincular dos sockets a exactamente la misma dirección de socket en BSD antes de esto opción.

Connect() ¿Devolviendo EADDRINUSE?

La mayoría de la gente sabe que bind() puede fallar con el error EADDRINUSE , sin embargo, cuando comienzas a jugar con la reutilización de direcciones, puedes encontrarte con la extraña situación de que connect() falla con ese error también. ¿Cómo puede ser esto? ¿Cómo puede una dirección remota, después de todo lo que connect agrega a un socket, estar ya en uso? Conectar múltiples enchufes exactamente a la misma dirección remota nunca ha sido un problema antes, entonces, ¿qué está fallando aquí?

Como dije en la parte superior de mi respuesta, una conexión se define por una tupla de cinco valores, ¿recuerdas? Y también dije que estos cinco valores deben ser únicos, de lo contrario, el sistema ya no puede distinguir dos conexiones, ¿verdad? Bueno, con la reutilización de direcciones, puede vincular dos sockets del mismo protocolo a la misma dirección y puerto de origen. Eso significa que tres de esos cinco valores ya son los mismos para estos dos enchufes. Si ahora intenta conectar ambos sockets también a la misma dirección y puerto de destino, crearía dos sockets conectados, cuyas tuplas son absolutamente idénticas. Esto no puede funcionar, al menos no para las conexiones TCP (las conexiones UDP no son conexiones reales de todos modos). Si llegaban datos para cualquiera de las dos conexiones, el sistema no podía saber a qué conexión pertenecen los datos. Al menos la dirección de destino o el puerto de destino debe ser diferente para cada conexión, de modo que el sistema no tenga problemas para identificar a qué conexión pertenecen los datos entrantes.

Entonces, si vincula dos sockets del mismo protocolo a la misma dirección y puerto de origen e intenta conectarlos a la misma dirección y puerto de destino, connect() en realidad fallará con el error EADDRINUSE para el segundo socket que intente conectar, lo que significa que ya está conectado un socket con una tupla idéntica de cinco valores.

Direcciones de multidifusión

La mayoría de la gente ignora el hecho de que existen direcciones de multidifusión, pero existen. Mientras que las direcciones de unidifusión se utilizan para la comunicación de uno a uno, las direcciones de multidifusión se utilizan para la comunicación de uno a muchos. La mayoría de las personas se dieron cuenta de las direcciones de multidifusión cuando se enteraron de IPv6, pero las direcciones de multidifusión también existían en IPv4, aunque esta función nunca se usó ampliamente en la Internet pública.

El significado de SO_REUSEADDR cambios para las direcciones de multidifusión, ya que permite vincular múltiples sockets a exactamente la misma combinación de dirección y puerto de multidifusión de origen. En otras palabras, para direcciones de multidifusión SO_REUSEADDR se comporta exactamente como SO_REUSEPORT para direcciones de unidifusión. En realidad, el código trata SO_REUSEADDR y SO_REUSEPORT de forma idéntica para las direcciones de multidifusión, eso significa que podría decir que SO_REUSEADDR implica SO_REUSEPORT para todas las direcciones de multidifusión y viceversa.


FreeBSD/OpenBSD/NetBSD

Todos estos son bifurcaciones bastante tardías del código BSD original, es por eso que los tres ofrecen las mismas opciones que BSD y también se comportan de la misma manera que en BSD.


macOS (MacOS X)

En esencia, macOS es simplemente un UNIX estilo BSD llamado "Darwin ", basado en una bifurcación bastante tardía del código BSD (BSD 4.3), que más tarde incluso se volvió a sincronizar con el código base FreeBSD 5 (actual en ese momento) para la versión Mac OS 10.3, para que Apple pudiera ganar Cumplimiento total de POSIX (macOS tiene certificación POSIX). A pesar de tener un microkernel en su núcleo ("Mach "), el resto del kernel ("XNU ") es básicamente un kernel BSD, y es por eso que macOS ofrece las mismas opciones que BSD y también se comportan de la misma manera que en BSD.

iOS/watchOS/tvOS

iOS es solo una bifurcación de macOS con un kernel ligeramente modificado y recortado, un conjunto de herramientas de espacio de usuario algo reducido y un conjunto de marco predeterminado ligeramente diferente. watchOS y tvOS son bifurcaciones de iOS, que se simplifican aún más (especialmente watchOS). Que yo sepa, todos se comportan exactamente como lo hace macOS.


Linux

Linux <3.9

Antes de Linux 3.9, solo la opción SO_REUSEADDR existió. Esta opción se comporta generalmente igual que en BSD con dos excepciones importantes:

  1. Siempre que un socket TCP de escucha (servidor) esté vinculado a un puerto específico, el SO_REUSEADDR La opción se ignora por completo para todos los sockets que apuntan a ese puerto. Vincular un segundo socket al mismo puerto solo es posible si también fue posible en BSD sin tener SO_REUSEADDR establecer. P.ej. no puede enlazar a una dirección comodín y luego a una más específica o al revés, ambas son posibles en BSD si configura SO_REUSEADDR . Lo que puede hacer es vincularse al mismo puerto y dos direcciones diferentes que no sean comodines, ya que eso siempre está permitido. En este aspecto Linux es más restrictivo que BSD.

  2. La segunda excepción es que para los sockets de clientes, esta opción se comporta exactamente como SO_REUSEPORT en BSD, siempre que ambos tuvieran esta bandera configurada antes de vincularse. La razón para permitir eso fue simplemente que es importante poder vincular múltiples sockets exactamente a la misma dirección de socket UDP para varios protocolos y como solía no haber SO_REUSEPORT antes de 3.9, el comportamiento de SO_REUSEADDR se modificó en consecuencia para llenar ese vacío. En ese aspecto, Linux es menos restrictivo que BSD.

Linux>=3.9

Linux 3.9 agregó la opción SO_REUSEPORT a Linux también. Esta opción se comporta exactamente como la opción en BSD y permite vincular exactamente la misma dirección y número de puerto siempre que todos los sockets tengan esta opción configurada antes de vincularlos.

Sin embargo, todavía hay dos diferencias con SO_REUSEPORT en otros sistemas:

  1. Para evitar el "secuestro de puertos", existe una limitación especial:¡Todos los sockets que deseen compartir la misma combinación de dirección y puerto deben pertenecer a procesos que compartan el mismo ID de usuario efectivo! Entonces, un usuario no puede "robar" los puertos de otro usuario. Esta es una magia especial para compensar un poco el SO_EXCLBIND faltante /SO_EXCLUSIVEADDRUSE banderas.

  2. Además, el núcleo realiza una "magia especial" para SO_REUSEPORT sockets que no se encuentran en otros sistemas operativos:para los sockets UDP, intenta distribuir los datagramas de manera uniforme, para los sockets de escucha TCP, intenta distribuir las solicitudes de conexión entrantes (aquellas aceptadas llamando a accept() ) uniformemente en todos los sockets que comparten la misma combinación de dirección y puerto. Por lo tanto, una aplicación puede abrir fácilmente el mismo puerto en múltiples procesos secundarios y luego usar SO_REUSEPORT para obtener un equilibrio de carga muy económico.


androide

Aunque todo el sistema Android es algo diferente de la mayoría de las distribuciones de Linux, en su núcleo funciona un kernel de Linux ligeramente modificado, por lo que todo lo que se aplica a Linux debería aplicarse también a Android.


Ventanas

Windows solo conoce el SO_REUSEADDR opción, no hay SO_REUSEPORT . Configuración SO_REUSEADDR en un socket en Windows se comporta como configurar SO_REUSEPORT y SO_REUSEADDR en un socket en BSD, con una excepción:

Antes de Windows 2003, un socket con SO_REUSEADDR siempre podría estar vinculado exactamente a la misma dirección de origen y puerto que un socket ya vinculado, incluso si el otro socket no tenía esta opción configurada cuando estaba vinculado . Este comportamiento permitía que una aplicación "robara" el puerto conectado de otra aplicación. ¡No hace falta decir que esto tiene importantes implicaciones de seguridad!

Microsoft se dio cuenta de eso y agregó otra opción de socket importante:SO_EXCLUSIVEADDRUSE . Configuración SO_EXCLUSIVEADDRUSE en un socket se asegura de que, si el enlace tiene éxito, la combinación de la dirección de origen y el puerto sea propiedad exclusiva de este socket y ningún otro socket pueda vincularse a ellos, ni siquiera. si tiene SO_REUSEADDR establecer.

Este comportamiento predeterminado se cambió primero en Windows 2003, Microsoft lo llama "Seguridad de socket mejorada" (nombre divertido para un comportamiento que es predeterminado en todos los demás sistemas operativos principales). Para más detalles solo visita esta página. Hay tres tablas:la primera muestra el comportamiento clásico (¡todavía en uso cuando se usan modos de compatibilidad!), la segunda muestra el comportamiento de Windows 2003 y posteriores cuando el bind() las llamadas las hace el mismo usuario, y la tercera cuando el bind() las llamadas son realizadas por diferentes usuarios.


Solaris

Solaris es el sucesor de SunOS. SunOS se basó originalmente en una bifurcación de BSD, SunOS 5 y más tarde se basó en una bifurcación de SVR4; sin embargo, SVR4 es una fusión de BSD, System V y Xenix, por lo que, hasta cierto punto, Solaris también es una bifurcación de BSD y un uno bastante temprano. Como resultado, Solaris solo conoce SO_REUSEADDR , no hay SO_REUSEPORT . El SO_REUSEADDR se comporta más o menos igual que en BSD. Que yo sepa, no hay forma de obtener el mismo comportamiento que SO_REUSEPORT en Solaris, eso significa que no es posible vincular dos sockets exactamente a la misma dirección y puerto.

Similar a Windows, Solaris tiene una opción para dar a un socket un enlace exclusivo. Esta opción se llama SO_EXCLBIND . Si esta opción se establece en un socket antes de vincularlo, establecer SO_REUSEADDR en otro socket no tiene efecto si los dos sockets se prueban para detectar un conflicto de direcciones. P.ej. si socketA está vinculado a una dirección comodín y socketB tiene SO_REUSEADDR habilitado y está vinculado a una dirección no comodín y al mismo puerto que socketA , este enlace normalmente tendrá éxito, a menos que socketA tenía SO_EXCLBIND habilitado, en cuyo caso fallará independientemente del SO_REUSEADDR bandera de socketB .


Otros sistemas

En caso de que su sistema no esté en la lista anterior, escribí un pequeño programa de prueba que puede usar para averiguar cómo maneja su sistema estas dos opciones. También si crees que mis resultados son incorrectos , primero ejecute ese programa antes de publicar cualquier comentario y posiblemente hacer afirmaciones falsas.

Todo lo que el código requiere para compilar es un poco de API POSIX (para las partes de la red) y un compilador C99 (en realidad, la mayoría de los compiladores que no son C99 funcionarán tan bien siempre que ofrezcan inttypes.h y stdbool.h; p.ej. gcc admitió ambos mucho antes de ofrecer compatibilidad completa con C99).

Todo lo que el programa necesita para ejecutarse es que al menos una interfaz en su sistema (que no sea la interfaz local) tenga una dirección IP asignada y que se establezca una ruta predeterminada que use esa interfaz. El programa recopilará esa dirección IP y la usará como la segunda "dirección específica".

Prueba todas las combinaciones posibles que se te ocurran:

  • Protocolo TCP y UDP
  • Sockets normales, sockets de escucha (servidor), sockets de multidifusión
  • SO_REUSEADDR establecer en socket1, socket2 o ambos sockets
  • SO_REUSEPORT establecer en socket1, socket2 o ambos sockets
  • Todas las combinaciones de direcciones que puedes hacer a partir de 0.0.0.0 (comodín), 127.0.0.1 (dirección específica) y la segunda dirección específica que se encuentra en su interfaz principal (para multidifusión es solo 224.1.2.3 en todas las pruebas)

e imprime los resultados en una bonita tabla. También funcionará en sistemas que no conocen SO_REUSEPORT , en cuyo caso esta opción simplemente no se prueba.

Lo que el programa no puede probar fácilmente es cómo SO_REUSEADDR actúa sobre sockets en TIME_WAIT state ya que es muy complicado forzar y mantener un socket en ese estado. Afortunadamente, la mayoría de los sistemas operativos parecen comportarse simplemente como BSD aquí y la mayoría de las veces los programadores simplemente pueden ignorar la existencia de ese estado.

Aquí está el código (no puedo incluirlo aquí, las respuestas tienen un límite de tamaño y el código empujaría esta respuesta por encima del límite).


La respuesta de Mecki es absolutamente perfecta, pero vale la pena agregar que FreeBSD también es compatible con SO_REUSEPORT_LB , que imita el SO_REUSEPORT de Linux comportamiento - equilibra la carga; ver setockopt(2)


Linux
  1. Cómo administrar y enumerar servicios en Linux

  2. ¿Qué es Podman y en qué se diferencia de Docker?

  3. Cómo:replicación y configuración de DRBD

  4. ¿Cómo manejar los eventos de socket de Linux POLLERR, POLLHUP y POLLNVAL?

  5. ¿En qué difieren ulimit -n y /proc/sys/fs/file-max?

¿Cómo instalar y usar Linux Screen?

Cómo cambiar el nombre de archivos y directorios en Linux

Cómo comprimir archivos y directorios en Linux

Cómo instalar y configurar SeedDMS

Cómo instalar y configurar Grafana

¿Cómo conectar en red Ubuntu y Windows 10?