¿Cómo difieren SO_REUSEADDR y SO_REUSEPORT?

663

El man pagesy documentaciones programador para las opciones de los conectores SO_REUSEADDRy SO_REUSEPORTson diferentes para diferentes sistemas operativos y, a menudo muy confuso. Algunos sistemas operativos ni siquiera tienen la opción SO_REUSEPORT. La WEB está llena de información contradictoria sobre este tema y, a menudo, puede encontrar información que solo es cierta para la implementación de un socket de un sistema operativo específico, que ni siquiera se menciona explícitamente en el texto.

Entonces, ¿cómo es exactamente SO_REUSEADDRdiferente a SO_REUSEPORT?

¿Son los sistemas sin SO_REUSEPORTmás limitaciones?

¿Y cuál es exactamente el comportamiento esperado si uso cualquiera de los dos en diferentes sistemas operativos?

Mecki
fuente

Respuestas:

1618

Bienvenido al maravilloso mundo de la portabilidad ... o más bien la falta de ella. Antes de comenzar a analizar estas dos opciones en detalle y analizar más a fondo cómo los manejan los diferentes sistemas operativos, debe tenerse 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 la copiaron más tarde obtuvieron características que faltaban en los sistemas que la 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 sobre ello incluso si no le importa escribir código para un sistema BSD.

Hay un par de conceptos básicos que debe conocer 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, no hay dos conexiones que puedan 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 la socket()función. La dirección de origen y el puerto se configuran con la bind()función. La dirección de destino y el puerto se configuran con la connect()función. Como UDP es un protocolo sin conexión, se pueden usar sockets UDP sin conectarlos. Sin embargo, está permitido conectarlos y, en algunos casos, es muy ventajoso para el código y el diseño general de la aplicación. En el modo sin conexión, los sockets UDP que no se vincularon explícitamente cuando los datos se envían por primera vez generalmente están vinculados automáticamente por el sistema, ya que un socket UDP no vinculado no puede recibir ningún dato (respuesta). Lo mismo es cierto para un socket TCP no vinculado, se vincula automáticamente antes de conectarse.

Si vincula explícitamente un socket, es posible vincularlo al puerto 0, lo que significa "cualquier puerto". Dado que un socket no puede vincularse realmente a todos los puertos existentes, el sistema tendrá que elegir un puerto específico en ese caso (generalmente de un rango predefinido de puertos de origen específicos del sistema operativo). Existe un comodín similar para la dirección de origen, que puede ser "cualquier dirección" ( 0.0.0.0en el caso de IPv4 y::en caso de IPv6). A diferencia del caso de los puertos, un socket realmente puede estar vinculado 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 adelante, el sistema tiene que elegir una dirección IP de origen específica, ya que no se puede conectar un socket y al mismo tiempo estar vinculado a cualquier dirección IP local. Dependiendo de la dirección de destino y del contenido de la tabla de enrutamiento, el sistema elegirá una dirección de origen apropiada y reemplazará el enlace "cualquiera" por un enlace a la dirección IP de origen elegida.

De manera predeterminada, no se pueden vincular dos sockets a la misma combinación de dirección de origen y puerto de origen. Mientras el puerto de origen sea diferente, la dirección de origen es irrelevante. La unión socketAa A:Xy socketBa B:Y, donde Ay Bson las direcciones y Xy Yson puertos, siempre es posible, siempre y cuando X != Yes cierto. Sin embargo, incluso si X == Y, el enlace sigue siendo posible siempre que sea A != Bcierto. Por ejemplo, socketApertenece a un programa de servidor FTP y está vinculado 192.168.0.1:21y socketBpertenece a otro programa de servidor FTP y está vinculado 10.0.0.1:21, ambos enlaces tendrán éxito. Sin embargo, tenga en cuenta que un socket puede estar vinculado localmente a "cualquier dirección". Si un zócalo está obligado a0.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, ya que 0.0.0.0entra 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 ser específicas del sistema operativo cuando la reutilización de direcciones entra en juego. Comenzamos con BSD, ya que, como dije anteriormente, es la madre de todas las implementaciones de socket.

BSD

SO_REUSEADDR

Si SO_REUSEADDRestá habilitado en un socket antes de vincularlo, el socket puede vincularse con éxito a menos que haya un conflicto con otro socket vinculado exactamente a la misma combinación de dirección de origen y puerto. Ahora puede preguntarse cómo es eso diferente de antes. La palabra clave es "exactamente". SO_REUSEADDRcambia principalmente la forma en que se tratan las direcciones comodín ("cualquier dirección IP") cuando se buscan conflictos.

Sin SO_REUSEADDR, la unión socketAa 0.0.0.0:21y después de unión socketBa 192.168.0.1:21fallará (con error EADDRINUSE), ya que 0.0.0.0 significa "cualquier dirección de IP local", por lo tanto todas las direcciones IP locales se consideran en uso por esta toma y esto incluye 192.168.0.1, también. Con SO_REUSEADDResto tendrá éxito, ya que 0.0.0.0y no192.168.0.1 son exactamente la misma dirección, una es un comodín para todas las direcciones locales y la otra es una dirección local muy específica. Tenga en cuenta que la declaración anterior es verdadera independientemente de en qué orden socketAy socketBestán vinculados; sin SO_REUSEADDRella siempre fallará, con SO_REUSEADDRella siempre tendrá éxito.

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

SO_REUSEADDR socketA socketB Resultado
-------------------------------------------------- -------------------
  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)
   EN 0.0.0.0:21 192.168.1.0:21 OK
   ENCENDIDO 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 supone que socketAya se ha vinculado correctamente a la dirección proporcionada socketA, luego socketBse crea, se SO_REUSEADDRconfigura o no, y finalmente se vincula a la dirección indicada socketB. Resultes el resultado de la operación de vinculación para socketB. Si la primera columna dice ON/OFF, el valor de SO_REUSEADDRes irrelevante para el resultado.

Está bien, SO_REUSEADDRtiene 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 las personas usan los SO_REUSEADDRprogramas de servidor en primer lugar. Para el otro uso importante de esta opción, debemos analizar más a fondo cómo funciona el protocolo TCP.

Un socket tiene un búfer de envío y si una llamada a la send()función tiene éxito, no significa que los datos solicitados realmente se hayan enviado realmente, solo significa que los datos se han agregado al búfer de envío. Para los sockets UDP, los datos generalmente se envían muy pronto, si no de inmediato, pero para los sockets TCP, puede haber un retraso relativamente largo entre agregar datos al búfer de envío y hacer que la implementación de TCP realmente envíe esos datos. Como resultado, cuando cierra un socket TCP, todavía puede haber datos pendientes en el búfer de envío, que aún no se ha enviado pero su código lo considera como enviado, ya que elsend()llamada exitosa. Si la implementación de TCP cerrara el socket inmediatamente a su solicitud, todos estos datos se perderían y su código ni siquiera sabría sobre eso. Se dice que TCP es un protocolo confiable y la pérdida de datos así no es muy confiable. Es por eso que un socket que todavía tiene datos para enviar entrará en un estado llamado TIME_WAITcuando lo cierre. En ese estado esperará hasta que todos los datos pendientes se hayan enviado con éxito o hasta que se agote el tiempo de espera, en cuyo caso el socket se cierra con fuerza.

La cantidad de tiempo que el kernel esperará antes de cerrar el socket, independientemente de si todavía tiene datos en vuelo o no, se llama Tiempo de espera . El tiempo de espera es globalmente configurable en la mayoría de los sistemas y por defecto es bastante largo (dos minutos es un valor común que encontrará en muchos sistemas). También se puede configurar por zócalo utilizando la opción de zócalo SO_LINGERque se puede usar para acortar o alargar el tiempo de espera e incluso para deshabilitarlo por completo. Sin embargo, deshabilitarlo por completo es una muy mala idea, ya que cerrar un socket TCP con gracia es un proceso ligeramente complejo e implica enviar y devolver un par de paquetes (así como reenviar esos paquetes en caso de que se pierdan) y todo este proceso cerrado. también está limitado por el tiempo de Linger. Si deshabilita la demora, es posible que su socket no solo pierda datos en vuelo, sino que también se cierre con fuerza en lugar de con gracia, lo que generalmente no se recomienda. Los detalles sobre cómo se cierra correctamente una conexión TCP están más allá del alcance de esta respuesta, si desea obtener más información, le recomiendo que eche un vistazo a esta página . E incluso si desactivó la persistencia SO_LINGER, si su proceso muere sin cerrar explícitamente el socket, BSD (y posiblemente otros sistemas) se mantendrán, ignorando lo que ha configurado. Esto sucederá, por ejemplo, si su código solo llamaexit()(bastante común para programas de servidor pequeños y simples) o el proceso se interrumpe por una señal (que incluye la posibilidad de que simplemente se bloquee debido a un acceso ilegal a la memoria). Por lo tanto, no hay nada que pueda hacer para asegurarse de que un socket nunca se demore en todas las circunstancias.

La pregunta es, ¿cómo trata el sistema un socket en estado TIME_WAIT? Si SO_REUSEADDRno se establece, TIME_WAITse considera que un socket en estado todavía está vinculado a la dirección de origen y al puerto, y cualquier intento de vincular un nuevo socket a la misma dirección y puerto fallará hasta que el socket se haya cerrado realmente, lo que puede llevar tanto tiempo como el tiempo de Linger configurado . Por lo tanto, 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_REUSEADDRestá configurado para el socket que está intentando vincular, otro socket vinculado a la misma dirección y puerto en estadoTIME_WAITsimplemente se ignora, después de todo ya está "medio muerto", y su socket puede unirse 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_WAITestado puede tener efectos secundarios inesperados, y generalmente no deseados, en caso de que el otro socket 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 SO_REUSEADDR. Todo lo escrito anteriormente funcionará siempre que el socket al que desea enlazar tenga habilitada la reutilización de direcciones. No es necesario que el otro zócalo, el que ya está enlazado o en TIME_WAITestado, también tenga este indicador activado cuando estaba enlazado. El código que decide si el enlace tendrá éxito o falla solo inspecciona el SO_REUSEADDRindicador del socket alimentado en la bind()llamada, para todos los demás conectores inspeccionados, este indicador ni siquiera se mira.

SO_REUSEPORT

SO_REUSEPORTes lo que la mayoría de la gente esperaría SO_REUSEADDRser. Básicamente, le SO_REUSEPORTpermite vincular un número arbitrario de sockets a exactamente la misma dirección de origen y puerto, siempre y cuando todos los sockets enlazados anteriores también se hayan SO_REUSEPORTestablecido antes de estar enlazados. Si el primer socket que está vinculado a una dirección y puerto no se ha SO_REUSEPORTestablecido, ningún otro socket se puede vincular exactamente a la misma dirección y puerto, independientemente de si este otro socket se ha SO_REUSEPORTestablecido o no, hasta que el primer socket libere su enlace nuevamente. A diferencia del caso del SO_REUESADDRmanejo del código SO_REUSEPORT, no solo verificará que el socket vinculado actualmente se haya SO_REUSEPORTestablecido, sino que también verificará que el socket con una dirección y un puerto en conflicto se había SO_REUSEPORTestablecido cuando estaba vinculado.

SO_REUSEPORTno implica SO_REUSEADDR. Esto significa que si un socket no se SO_REUSEPORTestableció cuando estaba vinculado y otro socket se SO_REUSEPORTconfiguró cuando está vinculado exactamente a la misma dirección y puerto, el enlace falla, lo que se espera, pero también falla si el otro socket ya está muriendo y está en TIME_WAITestado Para poder vincular un socket a las mismas direcciones y puerto que otro socket en TIME_WAITestado, se SO_REUSEADDRdebe establecer en ese socket o se SO_REUSEPORTdebe haber establecido en ambos sockets antes de vincularlos. Por supuesto, está permitido configurar ambos SO_REUSEPORTy SO_REUSEADDR, en un zócalo.

No hay mucho más que decir sobre el hecho SO_REUSEPORTde que se agregó más tarde SO_REUSEADDR, por eso no lo encontrará en muchas implementaciones de socket de otros sistemas, que "bifurcaron" el código BSD antes de agregar 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 esta opción.

Conectar () ¿Devuelve 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, también puedes encontrarte con la extraña situación que connect()falla con ese error. ¿Cómo puede ser esto? ¿Cómo puede una dirección remota, después de todo, eso es lo que conectar agrega a un socket, ya estar en uso? Conectar múltiples tomas a la misma dirección remota nunca ha sido un problema antes, entonces, ¿qué está pasando 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 podrá distinguir dos conexiones, ¿verdad? Bueno, con la reutilización de direcciones, puede vincular dos zócalos del mismo protocolo a la misma dirección de origen y puerto. Eso significa que tres de esos cinco valores ya son los mismos para estos dos sockets. 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 conexiones TCP (las conexiones UDP no son conexiones reales de todos modos). Si llegaron datos para cualquiera de las dos conexiones, el sistema no podría decir a qué conexión pertenecen los datos.

Por lo tanto, 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, en connect()realidad fallará con el error EADDRINUSEpara el segundo socket que intenta conectar, lo que significa que un el zócalo con una tupla idéntica de cinco valores ya está conectado.

Direcciones de multidifusión

La mayoría de las personas 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 uno a uno, las direcciones de multidifusión se utilizan para la comunicación 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, a pesar de que esta función nunca se usó ampliamente en Internet pública.

El significado de los SO_REUSEADDRcambios para las direcciones de multidifusión, ya que permite que varios sockets se unan exactamente a la misma combinación de dirección y puerto de multidifusión de origen. En otras palabras, para las direcciones de multidifusión se SO_REUSEADDRcomporta exactamente como SO_REUSEPORTpara las direcciones de unidifusión. En realidad, el código trata SO_REUSEADDRe SO_REUSEPORTidénticamente las direcciones de multidifusión, lo que significa que se podría decir que SO_REUSEADDRimplica SO_REUSEPORTpara todas las direcciones de multidifusión y viceversa.


FreeBSD / OpenBSD / NetBSD

Todos estos son tenedores bastante tardíos 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 de estilo BSD llamado " Darwin ", basado en una bifurcación bastante tardía del código BSD (BSD 4.3), que luego se sincronizó con FreeBSD (en ese momento actual) 5 bases de código para la versión Mac OS 10.3, para que Apple pueda obtener el pleno cumplimiento de POSIX (macOS tiene la 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 núcleo ligeramente modificado y recortado, un conjunto de herramientas de espacio de usuario algo despojado y un conjunto de marcos predeterminado ligeramente diferente. watchOS y tvOS son bifurcaciones de iOS, que se reducen aún más (especialmente watchOS). Que yo sepa, todos se comportan exactamente igual que macOS.


Linux

Linux <3.9

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

  1. Mientras un socket TCP (servidor) de escucha esté vinculado a un puerto específico, la SO_REUSEADDRopción se ignora por completo para todos los sockets que se dirigen a ese puerto. La vinculación de un segundo socket al mismo puerto solo es posible si también fue posible en BSD sin haberse SO_REUSEADDRconfigurado. Por ejemplo, no puede vincularse a una dirección comodín y luego a una más específica o al revés, ambas son posibles en BSD si establece SO_REUSEADDR. Lo que puede hacer es enlazar al mismo puerto y a dos direcciones diferentes que no sean comodines, como 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_REUSEPORTen BSD, siempre y cuando ambos tengan este indicador establecido antes de que se vincularan. La razón para permitir eso fue simplemente que es importante poder vincular múltiples sockets a exactamente la misma dirección de socket UDP para varios protocolos y, como solía haber no SO_REUSEPORTantes de 3.9, el comportamiento de SO_REUSEADDRse alteró en consecuencia para llenar ese vacío. . En ese aspecto, Linux es menos restrictivo que BSD.

Linux> = 3.9

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

Sin embargo, todavía hay dos diferencias con respecto SO_REUSEPORTa otros sistemas:

  1. Para evitar el "secuestro de puertos", hay una limitación especial: ¡ todos los sockets que desean compartir la misma dirección y combinación de puertos deben pertenecer a procesos que compartan la misma ID de usuario efectiva! Por lo tanto, un usuario no puede "robar" puertos de otro usuario. Esta es una magia especial para compensar un poco las faltas SO_EXCLBIND/ SO_EXCLUSIVEADDRUSEbanderas.

  2. Además, el kernel realiza una "magia especial" para los SO_REUSEPORTsockets que no se encuentran en otros sistemas operativos: para los sockets UDP, intenta distribuir datagramas de manera uniforme, para los sockets de escucha TCP, intenta distribuir las solicitudes de conexión entrantes (las aceptadas llamando accept()) de manera uniforme 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 usarla SO_REUSEPORTpara 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 tanto, todo lo que se aplica a Linux también debería aplicarse a Android.


Ventanas

Windows solo conoce la SO_REUSEADDRopción, no la hay SO_REUSEPORT. La configuración SO_REUSEADDRen un socket en Windows se comporta como la configuración SO_REUSEPORTy SO_REUSEADDRen un socket en BSD, con una excepción: un socket con SO_REUSEADDRsiempre puede unirse 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 establecido cuando estaba vinculado . Este comportamiento es algo peligroso porque permite que una aplicación "robe" el puerto conectado de otra aplicación. No hace falta decir que esto puede tener importantes implicaciones de seguridad. Microsoft se dio cuenta de que esto podría ser un problema y, por lo tanto, agregó otra opción de socket SO_EXCLUSIVEADDRUSE. AjusteSO_EXCLUSIVEADDRUSEen un socket se asegura de que si el enlace tiene éxito, la combinación de la dirección de origen y el puerto es propiedad exclusiva de este socket y ningún otro socket puede unirse a ellos, ni siquiera si se ha SO_REUSEADDRestablecido.

Para obtener aún más detalles sobre cómo funcionan los indicadores SO_REUSEADDRy SO_EXCLUSIVEADDRUSEen Windows, cómo influyen en la vinculación / nueva vinculación, Microsoft proporcionó amablemente una tabla similar a mi tabla cerca de la parte superior de esa respuesta. Simplemente visite esta página y desplácese hacia abajo un poco. En realidad, hay tres tablas, la primera muestra el comportamiento anterior (anterior a Windows 2003), la segunda el comportamiento (Windows 2003 y posterior) y la tercera muestra cómo cambia el comportamiento en Windows 2003 y más tarde si las bind()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 BSD, y un uno bastante temprano. Como resultado, Solaris solo sabe que SO_REUSEADDRno existe SO_REUSEPORT. El se SO_REUSEADDRcomporta más o menos igual que en BSD. Hasta donde sé, no hay forma de obtener el mismo comportamiento que SO_REUSEPORTen Solaris, eso significa que no es posible vincular dos zócalos a exactamente 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 tiene nombre SO_EXCLBIND. Si esta opción se configura en un socket antes de vincularla, la configuración SO_REUSEADDRen otro socket no tiene ningún efecto si se prueba un conflicto de direcciones en los dos sockets. Por ejemplo, si socketAestá vinculado a una dirección comodín y se socketBha SO_REUSEADDRhabilitado y está vinculado a una dirección no comodín y al mismo puerto que socketA, este enlace normalmente tendrá éxito, a menos que se socketAhaya SO_EXCLBINDhabilitado, en cuyo caso fallará independientemente del SO_REUSEADDRindicador 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. Además, si cree que mis resultados son incorrectos , primero ejecute ese programa antes de publicar cualquier comentario y posiblemente hacer afirmaciones falsas.

Todo lo que requiere el código 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 como se ofrecen inttypes.hy stdbool.h; por ejemplo, se gccadmiten ambos mucho antes de ofrecer soporte C99 completo) .

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 reunirá 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
  • Tomas normales, tomas de escucha (servidor), tomas de multidifusión
  • SO_REUSEADDR establecido en socket1, socket2, o ambos enchufes
  • SO_REUSEPORT establecido en socket1, socket2, o ambos enchufes
  • Todas las combinaciones de direcciones que puede hacer 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 la multidifusión es solo 224.1.2.3en todas las pruebas)

e imprime los resultados en una bonita tabla. También funcionará en sistemas que no saben 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_REUSEADDRactúa en los zócalos en TIME_WAITestado, ya que es muy difícil forzar y mantener un zócalo 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).

Mecki
fuente
99
Por ejemplo, "dirección de origen" realmente debería ser "dirección local", los siguientes tres campos también. La vinculación con INADDR_ANYno vincula las direcciones locales existentes, sino también todas las futuras. listenciertamente crea sockets con el mismo protocolo exacto, dirección local y puerto local, a pesar de que dijiste que no es posible.
Ben Voigt
99
@Ben Source y Destination son los términos oficiales utilizados para el direccionamiento IP (a lo que me refiero principalmente). Local y Remoto no tendría sentido, ya que la dirección Remota puede ser una dirección "Local" y lo opuesto a Destino es Origen y no Local. No sé cuál es su problema INADDR_ANY, nunca dije que no se uniría a direcciones futuras. Y listenno crea ningún socket, lo que hace que toda tu oración sea un poco extraña.
Mecki
77
@Ben Cuando se agrega una nueva dirección al sistema, también es una "dirección local existente", recién comienza a existir. No dije "a todas las direcciones locales existentes actualmente ". En realidad, incluso digo que el zócalo está realmente unido al comodín , lo que significa que el zócalo está vinculado a lo que coincida con este comodín, ahora, mañana y dentro de cien años. Similar a la fuente y el destino, solo estás jugando aquí. ¿Tiene alguna contribución técnica real que hacer?
Mecki
8
@Mecki: ¿Realmente crees que la palabra existente incluye cosas que no existen ahora pero que lo harán en el futuro? El origen y el destino no son una trampa. Cuando los paquetes entrantes se corresponden con un socket, ¿está diciendo que la dirección de destino en el paquete se comparará con una dirección "fuente" del socket? Eso está mal y lo sabes, ya dijiste que el origen y el destino son opuestos. La dirección local en el socket se compara con la dirección de destino de los paquetes entrantes y se coloca en la dirección de origen en los paquetes salientes.
Ben Voigt
10
@Mecki: Eso tiene mucho más sentido si dice "La dirección local del socket es la dirección de origen de los paquetes salientes y la dirección de destino de los paquetes entrantes". Los paquetes tienen direcciones de origen y destino. Los hosts y los sockets en los hosts no lo hacen. Para los sockets de datagramas, ambos pares son iguales. Para los sockets TCP, debido al apretón de manos de tres vías, hay un originador (cliente) y un respondedor (servidor), pero eso todavía no significa que la conexión o los sockets conectados tengan una fuente y un destino , ya que el tráfico fluye en ambos sentidos.
Ben Voigt
1

La respuesta de Mecki es absolutamente perfecta, pero vale la pena agregar que FreeBSD también es compatible SO_REUSEPORT_LB, lo que imita el SO_REUSEPORTcomportamiento de Linux : equilibra la carga; ver setsockopt (2)

Edward Tomasz Napierala
fuente
Buen hallazgo. No lo vi en las páginas del manual cuando lo revisé. Definitivamente vale la pena mencionarlo, ya que puede ser muy útil al portar software de Linux a FreeBSD.
Mecki hace