En quoi SO_REUSEADDR et SO_REUSEPORT diffèrent-ils?

663

Le man pagesprogrammeur et les options Documentations pour socket SO_REUSEADDRet SO_REUSEPORTsont différents pour différents systèmes d'exploitation et souvent très confus. Certains systèmes d'exploitation n'ont même pas la possibilité SO_REUSEPORT. Le WEB regorge d'informations contradictoires à ce sujet et souvent vous pouvez trouver des informations qui ne sont vraies que pour une implémentation de socket d'un système d'exploitation spécifique, qui peuvent même ne pas être explicitement mentionnées dans le texte.

Alors, comment est exactement SO_REUSEADDRdifférent de SO_REUSEPORT?

Les systèmes sont-ils sans SO_REUSEPORTplus limités?

Et quel est exactement le comportement attendu si j'utilise l'un ou l'autre sur différents systèmes d'exploitation?

Mecki
la source

Réponses:

1618

Bienvenue dans le monde merveilleux de la portabilité ... ou plutôt de son absence. Avant de commencer à analyser ces deux options en détail et d'approfondir la façon dont les différents systèmes d'exploitation les gèrent, il convient de noter que l'implémentation de socket BSD est la mère de toutes les implémentations de socket. Fondamentalement, tous les autres systèmes ont copié l'implémentation du socket BSD à un moment donné (ou au moins ses interfaces), puis ont commencé à le faire évoluer par eux-mêmes. Bien sûr, l'implémentation du socket BSD a également évolué en même temps et donc les systèmes qui l'ont copié plus tard ont obtenu des fonctionnalités qui manquaient dans les systèmes qui l'ont copié plus tôt. Comprendre l'implémentation de socket BSD est la clé pour comprendre toutes les autres implémentations de socket, donc vous devriez lire à ce sujet même si vous ne vous souciez jamais d'écrire du code pour un système BSD.

Il y a quelques notions de base que vous devez connaître avant d'examiner ces deux options. Une connexion TCP / UDP est identifiée par un tuple de cinq valeurs:

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

Toute combinaison unique de ces valeurs identifie une connexion. Par conséquent, deux connexions ne peuvent pas avoir les mêmes cinq valeurs, sinon le système ne pourrait plus distinguer ces connexions.

Le protocole d'un socket est défini lors de la création d'un socket avec la socket()fonction. L'adresse source et le port sont définis avec la bind()fonction. L'adresse et le port de destination sont définis avecconnect() fonction. UDP étant un protocole sans connexion, les sockets UDP peuvent être utilisés sans les connecter. Pourtant, il est autorisé de les connecter et dans certains cas, très avantageux pour votre code et la conception générale de votre application. En mode sans connexion, les sockets UDP qui n'étaient pas explicitement liés lorsque des données leur sont envoyées pour la première fois sont généralement automatiquement liés par le système, car un socket UDP non lié ne peut pas recevoir de données (de réponse). Il en va de même pour un socket TCP non lié, il est automatiquement lié avant d'être connecté.

Si vous liez explicitement un socket, il est possible de le lier au port 0, ce qui signifie "n'importe quel port". Puisqu'un socket ne peut pas vraiment être lié à tous les ports existants, le système devra choisir lui-même un port spécifique dans ce cas (généralement à partir d'une plage de ports source prédéfinie et spécifique au système d'exploitation). Un caractère générique similaire existe pour l'adresse source, qui peut être "n'importe quelle adresse" ( 0.0.0.0dans le cas d'IPv4 et::en cas d'IPv6). Contrairement au cas des ports, un socket peut vraiment être lié à "n'importe quelle adresse" ce qui signifie "toutes les adresses IP source de toutes les interfaces locales". Si le socket est connecté ultérieurement, le système doit choisir une adresse IP source spécifique, car un socket ne peut pas être connecté et en même temps lié à une adresse IP locale. En fonction de l'adresse de destination et du contenu de la table de routage, le système choisira une adresse source appropriée et remplacera la liaison "any" par une liaison à l'adresse IP source choisie.

Par défaut, deux sockets ne peuvent pas être liés à la même combinaison d'adresse source et de port source. Tant que le port source est différent, l'adresse source n'est en fait pas pertinente. La liaison socketAvers A:Xet socketBvers B:Y, où Aet Bsont des adresses et Xet Ysont des ports, est toujours possible tant que cela X != Yest vrai. Cependant, même si X == Y, la liaison est toujours possible tant qu'elle reste A != Bvraie. Par exemple, socketAappartient à un programme de serveur FTP et est lié à 192.168.0.1:21et socketBappartient à un autre programme de serveur FTP et est lié à 10.0.0.1:21, les deux liaisons réussiront. Gardez cependant à l'esprit qu'un socket peut être lié localement à "n'importe quelle adresse". Si une socket est liée à0.0.0.0:21, il est lié à toutes les adresses locales existantes en même temps et, dans ce cas, aucun autre socket ne peut être lié au port21, quelle que soit l'adresse IP spécifique à laquelle il essaie de se lier, car cela 0.0.0.0entre en conflit avec toutes les adresses IP locales existantes.

Tout ce qui a été dit jusqu'à présent est à peu près égal pour tous les principaux systèmes d'exploitation. Les choses commencent à devenir spécifiques au système d'exploitation lorsque la réutilisation des adresses entre en jeu. Nous commençons par BSD, car comme je l'ai dit ci-dessus, c'est la mère de toutes les implémentations de socket.

BSD

SO_REUSEADDR

Si SO_REUSEADDRest activé sur un socket avant de le lier, le socket peut être lié avec succès, sauf s'il existe un conflit avec un autre socket lié exactement à la même combinaison d'adresse source et de port. Maintenant, vous vous demandez peut-être en quoi cela est-il différent qu'auparavant? Le mot clé est "exactement". SO_REUSEADDRmodifie principalement la façon dont les adresses génériques ("toute adresse IP") sont traitées lors de la recherche de conflits.

Sans SO_REUSEADDR, la liaison socketAà 0.0.0.0:21puis la liaison socketBà 192.168.0.1:21échouera (avec erreur EADDRINUSE), car 0.0.0.0 signifie "toute adresse IP locale", donc toutes les adresses IP locales sont considérées comme utilisées par ce socket et cela inclut 192.168.0.1également. Avec SO_REUSEADDRelle réussira, car 0.0.0.0et 192.168.0.1sont pas exactement la même adresse, on est un caractère générique pour toutes les adresses locales et l'autre est une adresse locale très spécifique. Notez que l'énoncé ci-dessus est vrai quel que soit l'ordre socketAet socketBles liens; sans SO_REUSEADDRelle échouera toujours, avec SO_REUSEADDRelle réussira toujours.

Pour vous donner un meilleur aperçu, faisons un tableau ici et listons toutes les combinaisons possibles:

SO_REUSEADDR socketA socketB Résultat
-------------------------------------------------- -------------------
  ON / OFF 192.168.0.1:21 192.168.0.1:21 Erreur (EADDRINUSE)
  MARCHE / ARRÊT 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 Erreur 192.168.1.0:21 (EADDRINUSE)
   OFF 192.168.1.0:21 0.0.0.0:21 Erreur (EADDRINUSE)
   MARCHE 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 Erreur (EADDRINUSE)

Le tableau ci-dessus suppose qu'il socketAa déjà été lié avec succès à l'adresse indiquée pour socketA, puis socketBest créé, est SO_REUSEADDRdéfini ou non, et enfin est lié à l'adresse indiquée pour socketB. Resultest le résultat de l'opération de liaison pour socketB. Si la première colonne indique ON/OFF, la valeur de SO_REUSEADDRn'est pas pertinente pour le résultat.

D'accord, SO_REUSEADDRa un effet sur les adresses génériques, bon à savoir. Pourtant, ce n'est pas seulement son effet. Il y a un autre effet bien connu qui est aussi la raison pour laquelle la plupart des gens utilisent SO_REUSEADDRdans les programmes serveur en premier lieu. Pour l'autre utilisation importante de cette option, nous devons approfondir le fonctionnement du protocole TCP.

Une socket a un tampon d'envoi et si un appel à la send()fonction réussit, cela ne signifie pas que les données demandées ont réellement été réellement envoyées, cela signifie seulement que les données ont été ajoutées au tampon d'envoi. Pour les sockets UDP, les données sont généralement envoyées très rapidement, sinon immédiatement, mais pour les sockets TCP, il peut y avoir un délai relativement long entre l'ajout de données au tampon d'envoi et le fait que l'implémentation TCP envoie réellement ces données. Par conséquent, lorsque vous fermez un socket TCP, il peut toujours y avoir des données en attente dans le tampon d'envoi, qui n'ont pas encore été envoyées mais votre code les considère comme envoyées, car lesend()l'appel a réussi. Si l'implémentation TCP fermait le socket immédiatement à votre demande, toutes ces données seraient perdues et votre code n'en serait même pas informé. TCP est considéré comme un protocole fiable et la perte de données comme celle-ci n'est pas très fiable. C'est pourquoi un socket qui a encore des données à envoyer entrera dans un état appelé TIME_WAITlorsque vous le fermerez. Dans cet état, il attendra que toutes les données en attente aient été envoyées avec succès ou jusqu'à ce qu'un délai soit atteint, auquel cas le socket est fermé de force.

La durée pendant laquelle le noyau attendra avant de fermer le socket, qu'il ait ou non encore des données en vol, est appelée Linger Time . Le temps d'attente est globalement configurable sur la plupart des systèmes et par défaut assez long (deux minutes est une valeur courante que vous trouverez sur de nombreux systèmes). Il est également configurable par socket en utilisant l'option socket SO_LINGERqui peut être utilisée pour raccourcir ou allonger le délai d'attente, et même pour le désactiver complètement. La désactiver complètement est une très mauvaise idée, cependant, car fermer un socket TCP avec élégance est un processus légèrement complexe et implique l'envoi et le retour de quelques paquets (ainsi que le renvoi de ces paquets en cas de perte) et tout ce processus de fermeture est également limité par le Linger Time. Si vous désactivez la persistance, votre socket peut non seulement perdre des données en vol, mais elle est également toujours fermée avec force au lieu de gracieusement, ce qui n'est généralement pas recommandé. Les détails sur la façon dont une connexion TCP est fermée avec élégance dépassent le cadre de cette réponse, si vous souhaitez en savoir plus, je vous recommande de consulter cette page . Et même si vous avez désactivé la persistance SO_LINGER, si votre processus meurt sans fermer explicitement le socket, BSD (et éventuellement d'autres systèmes) persistera néanmoins, ignorant ce que vous avez configuré. Cela se produira par exemple si votre code appelle simplementexit()(assez courant pour les petits programmes de serveur simples) ou le processus est tué par un signal (qui inclut la possibilité qu'il se bloque simplement à cause d'un accès illégal à la mémoire). Il n'y a donc rien que vous puissiez faire pour vous assurer qu'une prise ne persistera jamais en toutes circonstances.

La question est, comment le système traite-t-il un socket en état TIME_WAIT? Si SO_REUSEADDRn'est pas défini, un socket en état TIME_WAITest considéré comme étant toujours lié à l'adresse et au port source et toute tentative de lier un nouveau socket à la même adresse et au même port échouera jusqu'à ce que le socket soit vraiment fermé, ce qui peut prendre aussi longtemps comme heure de sonnerie configurée . Ne vous attendez donc pas à pouvoir lier à nouveau l'adresse source d'un socket immédiatement après sa fermeture. Dans la plupart des cas, cela échouera. Cependant, si SO_REUSEADDRest défini pour le socket que vous essayez de lier, un autre socket lié à la même adresse et au même port dans l'étatTIME_WAITest simplement ignoré, après tout, il est déjà "à moitié mort", et votre socket peut se lier exactement à la même adresse sans aucun problème. Dans ce cas, il ne joue aucun rôle que l'autre socket peut avoir exactement la même adresse et le même port. Notez que lier un socket à exactement la même adresse et le même port qu'un socket mourant dans l' TIME_WAITétat peut avoir des effets secondaires inattendus et généralement indésirables si l'autre socket est toujours "au travail", mais cela dépasse le cadre de cette réponse et heureusement, ces effets secondaires sont plutôt rares en pratique.

Il y a une dernière chose que vous devez savoir SO_REUSEADDR. Tout ce qui est écrit ci-dessus fonctionnera tant que la socket à laquelle vous souhaitez vous connecter a la réutilisation d'adresse activée. Il n'est pas nécessaire que l'autre socket, celle qui est déjà liée ou soit dans un TIME_WAITétat, ait également défini ce drapeau lors de sa liaison. Le code qui décide de la réussite ou de l'échec de la liaison inspecte uniquement l' SO_REUSEADDRindicateur du socket introduit dans l' bind()appel, pour tous les autres sockets inspectés, cet indicateur n'est même pas examiné.

SO_REUSEPORT

SO_REUSEPORTest ce que la plupart des gens s'attendent SO_REUSEADDRà être. Fondamentalement, SO_REUSEPORTvous permet de lier un nombre arbitraire de sockets à exactement la même adresse source et le même port tant que tous les sockets liés précédemment ont également été SO_REUSEPORTdéfinis avant d'être liés. Si le premier socket lié à une adresse et un port n'a pas été SO_REUSEPORTdéfini, aucun autre socket ne peut être lié exactement à la même adresse et au même port, que cet autre socket soit SO_REUSEPORTdéfini ou non, jusqu'à ce que le premier socket libère à nouveau sa liaison. Contrairement au cas du SO_REUESADDRtraitement du code SO_REUSEPORT, non seulement vérifiera que le socket actuellement lié a été SO_REUSEPORTdéfini, mais il vérifiera également que le socket avec une adresse et un port en conflit avait été SO_REUSEPORTdéfini lors de sa liaison.

SO_REUSEPORTn'implique pas SO_REUSEADDR. Cela signifie que si un socket n'a pas été SO_REUSEPORTdéfini lorsqu'il était lié et qu'un autre socket a SO_REUSEPORTdéfini lorsqu'il est lié exactement à la même adresse et au même port, la liaison échoue, ce qui est attendu, mais il échoue également si l'autre socket est déjà en train de mourir et est en TIME_WAITétat. Pour pouvoir lier un socket aux mêmes adresses et ports qu'un autre socket en TIME_WAITétat, il faut soit SO_REUSEADDRêtre défini sur ce socket, soit SO_REUSEPORTavoir été défini sur les deux sockets avant de les lier. Bien sûr, il est autorisé de définir les deux, SO_REUSEPORTet SO_REUSEADDR, sur un socket.

Il n'y a pas grand-chose d'autre à dire sur le SO_REUSEPORTfait qu'il a été ajouté plus tard que SO_REUSEADDR, c'est pourquoi vous ne le trouverez pas dans de nombreuses implémentations de socket d'autres systèmes, qui "bifurquaient" le code BSD avant l'ajout de cette option, et qu'il n'y avait pas façon de lier deux sockets à exactement la même adresse de socket dans BSD avant cette option.

Connect () Retourner EADDRINUSE?

La plupart des gens savent que bind()l'erreur peut échouer EADDRINUSE, cependant, lorsque vous commencez à jouer avec la réutilisation des adresses, vous pouvez également rencontrer l'étrange situation qui connect()échoue avec cette erreur. Comment se peut-il? Comment une adresse distante, après tout ce que la connexion ajoute à un socket, peut-elle déjà être utilisée? La connexion de plusieurs sockets à exactement la même adresse distante n'a jamais été un problème auparavant, alors qu'est-ce qui ne va pas ici?

Comme je l'ai dit tout en haut de ma réponse, une connexion est définie par un tuple de cinq valeurs, vous vous en souvenez? Et j'ai aussi dit que ces cinq valeurs doivent être uniques sinon le système ne peut plus distinguer deux connexions, non? Eh bien, avec la réutilisation des adresses, vous pouvez lier deux sockets du même protocole à la même adresse source et au même port. Cela signifie que trois de ces cinq valeurs sont déjà les mêmes pour ces deux sockets. Si vous essayez maintenant de connecter ces deux sockets également à la même adresse et au même port de destination, vous créerez deux sockets connectés, dont les tuples sont absolument identiques. Cela ne peut pas fonctionner, du moins pas pour les connexions TCP (les connexions UDP ne sont pas de vraies connexions de toute façon). Si des données arrivaient pour l'une des deux connexions, le système ne pouvait pas savoir à quelle connexion les données appartenaient.

Donc, si vous liez deux sockets du même protocole à la même adresse source et au même port et essayez de les connecter tous les deux à la même adresse et au même port de destination, connect()échouera en fait avec l'erreur EADDRINUSEpour le deuxième socket que vous essayez de connecter, ce qui signifie qu'un socket avec un tuple identique de cinq valeurs est déjà connecté.

Adresses de multidiffusion

La plupart des gens ignorent le fait qu'il existe des adresses de multidiffusion, mais elles existent. Alors que les adresses de monodiffusion sont utilisées pour les communications un à un, les adresses de multidiffusion sont utilisées pour les communications un à plusieurs. La plupart des gens ont découvert les adresses de multidiffusion lorsqu'ils ont découvert IPv6, mais les adresses de multidiffusion existaient également dans IPv4, même si cette fonctionnalité n'a jamais été largement utilisée sur Internet public.

La signification des SO_REUSEADDRchangements pour les adresses de multidiffusion car elle permet à plusieurs sockets d'être liés exactement à la même combinaison d'adresse et de port de multidiffusion source. En d'autres termes, pour les adresses multicast SO_REUSEADDRse comporte exactement comme SO_REUSEPORTpour les adresses unicast. En fait, le code traite SO_REUSEADDRet SO_REUSEPORTidentiquement pour les adresses de multidiffusion, cela signifie que vous pouvez dire que cela SO_REUSEADDRimplique SO_REUSEPORTpour toutes les adresses de multidiffusion et vice versa.


FreeBSD / OpenBSD / NetBSD

Ce sont toutes des fourches assez tardives du code BSD d'origine, c'est pourquoi elles offrent toutes les trois les mêmes options que BSD et se comportent également de la même manière qu'en BSD.


macOS (MacOS X)

À la base, macOS est simplement un UNIX de style BSD nommé " Darwin ", basé sur une fourche assez tardive du code BSD (BSD 4.3), qui a ensuite été même resynchronisé avec FreeBSD (à cette époque) Base de 5 codes pour la version Mac OS 10.3, afin qu'Apple puisse obtenir une conformité POSIX complète (macOS est certifié POSIX). En dépit d'avoir un micro-noyau à sa base (" Mach "), le reste du noyau (" XNU ") est fondamentalement juste un noyau BSD, et c'est pourquoi macOS offre les mêmes options que BSD et ils se comportent également de la même manière que dans BSD .

iOS / watchOS / tvOS

iOS n'est qu'un fork de macOS avec un noyau légèrement modifié et découpé, un ensemble d'outils d'espace utilisateur quelque peu allégé et un ensemble de framework par défaut légèrement différent. watchOS et tvOS sont des fourches iOS, qui sont encore plus dépouillées (en particulier watchOS). À ma connaissance, ils se comportent tous exactement comme macOS.


Linux

Linux <3,9

Avant Linux 3.9, seule l'option SO_REUSEADDRexistait. Cette option se comporte généralement de la même manière que dans BSD avec deux exceptions importantes:

  1. Tant qu'un socket TCP d'écoute (serveur) est lié à un port spécifique, l' SO_REUSEADDRoption est entièrement ignorée pour tous les sockets ciblant ce port. La liaison d'un deuxième socket au même port n'est possible que si elle était également possible dans BSD sans avoir SO_REUSEADDRdéfini. Par exemple, vous ne pouvez pas vous lier à une adresse générique, puis à une adresse plus spécifique ou inversement, les deux sont possibles dans BSD si vous définissez SO_REUSEADDR. Ce que vous pouvez faire, c'est que vous pouvez vous lier au même port et à deux adresses non génériques différentes, comme cela est toujours autorisé. Sous cet aspect, Linux est plus restrictif que BSD.

  2. La deuxième exception est que pour les sockets client, cette option se comporte exactement comme SO_REUSEPORTdans BSD, tant que les deux avaient cet indicateur défini avant d'être liés. La raison de cette autorisation était simplement qu'il était important de pouvoir lier plusieurs sockets à exactement à la même adresse de socket UDP pour divers protocoles et comme il n'y en avait pas SO_REUSEPORTavant 3.9, le comportement de a SO_REUSEADDRété modifié en conséquence pour combler cette lacune . Sous cet aspect, Linux est moins restrictif que BSD.

Linux> = 3,9

Linux 3.9 a également ajouté l'option SO_REUSEPORTà Linux. Cette option se comporte exactement comme l'option dans BSD et permet de se lier exactement à la même adresse et au même numéro de port tant que tous les sockets ont cette option définie avant de les lier.

Pourtant, il existe encore deux différences par rapport aux SO_REUSEPORTautres systèmes:

  1. Pour éviter le "détournement de port", il existe une limitation particulière: tous les sockets qui souhaitent partager la même adresse et la même combinaison de ports doivent appartenir à des processus qui partagent le même ID utilisateur effectif! Un utilisateur ne peut donc pas "voler" les ports d'un autre utilisateur. C'est une magie spéciale pour compenser quelque peu les drapeaux SO_EXCLBIND/ manquants SO_EXCLUSIVEADDRUSE.

  2. De plus, le noyau effectue une "magie spéciale" pour les SO_REUSEPORTsockets qui ne se trouve pas dans d'autres systèmes d'exploitation: pour les sockets UDP, il essaie de distribuer les datagrammes de manière égale, pour les sockets d'écoute TCP, il essaie de distribuer les demandes de connexion entrantes (celles acceptées en appelant accept()) uniformément sur toutes les sockets qui partagent la même adresse et la même combinaison de ports. Ainsi, une application peut facilement ouvrir le même port dans plusieurs processus enfants, puis l'utiliser SO_REUSEPORTpour obtenir un équilibrage de charge très peu coûteux.


Android

Même si l'ensemble du système Android est quelque peu différent de la plupart des distributions Linux, dans son cœur fonctionne un noyau Linux légèrement modifié, donc tout ce qui s'applique à Linux devrait également s'appliquer à Android.


les fenêtres

Windows ne connaît que l' SO_REUSEADDRoption, il n'y en a pas SO_REUSEPORT. Le paramétrage SO_REUSEADDRsur un socket dans Windows se comporte comme le paramétrage SO_REUSEPORTet SO_REUSEADDRsur un socket dans BSD, à une exception près: un socket avec SO_REUSEADDRpeut toujours se lier exactement à la même adresse source et au même port qu'un socket déjà lié, même si l'autre socket n'avait pas cette option définir quand il était lié . Ce comportement est quelque peu dangereux car il permet à une application de "voler" le port connecté d'une autre application. Inutile de dire que cela peut avoir des implications majeures sur la sécurité. Microsoft a réalisé que cela pouvait être un problème et a donc ajouté une autre option de socket SO_EXCLUSIVEADDRUSE. RéglageSO_EXCLUSIVEADDRUSEsur un socket s'assure que si la liaison réussit, la combinaison de l'adresse source et du port est la propriété exclusive de ce socket et aucun autre socket ne peut s'y lier, même s'il est SO_REUSEADDRdéfini.

Pour encore plus de détails sur la façon dont les indicateurs SO_REUSEADDRet SO_EXCLUSIVEADDRUSEfonctionnent sous Windows, comment ils influencent la liaison / la nouvelle liaison, Microsoft a aimablement fourni un tableau similaire à mon tableau en haut de cette réponse. Visitez cette page et faites défiler un peu. En fait, il y a trois tableaux, le premier montre l'ancien comportement (antérieur à Windows 2003), le second le comportement (Windows 2003 et supérieur) et le troisième montre comment le comportement change dans Windows 2003 et plus tard si les bind()appels sont effectués par différents utilisateurs.


Solaris

Solaris est le successeur de SunOS. SunOS était à l'origine basé sur une fourchette de BSD, SunOS 5 et plus tard était basé sur une fourchette de SVR4, mais SVR4 est une fusion de BSD, System V et Xenix, donc jusqu'à un certain point Solaris est également une fourchette BSD et un assez tôt. Par conséquent, Solaris ne sait queSO_REUSEADDR qu'il n'y en a pas SO_REUSEPORT. Le SO_REUSEADDRcomportement est à peu près le même que dans BSD. Pour autant que je sache, il n'y a aucun moyen d'obtenir le même comportement que SO_REUSEPORTdans Solaris, cela signifie qu'il n'est pas possible de lier deux sockets à exactement la même adresse et le même port.

Semblable à Windows, Solaris a une option pour donner à un socket une liaison exclusive. Cette option est nommée SO_EXCLBIND. Si cette option est définie sur un socket avant de le lier, la définition SO_REUSEADDRd'un autre socket n'a aucun effet si les deux sockets sont testés pour un conflit d'adresse. Par exemple, si socketAest lié à une adresse générique et socketBa SO_REUSEADDRactivé et est lié à une adresse non générique et au même port que socketA, cette liaison réussira normalement, sauf si elle socketAétait SO_EXCLBINDactivée, auquel cas elle échouera quel que soit l' SO_REUSEADDRindicateur de socketB.


Autres systèmes

Dans le cas où votre système n'est pas répertorié ci-dessus, j'ai écrit un petit programme de test que vous pouvez utiliser pour savoir comment votre système gère ces deux options. De plus, si vous pensez que mes résultats sont erronés , veuillez d'abord exécuter ce programme avant de poster des commentaires et éventuellement de fausses déclarations.

Tout ce dont le code a besoin pour construire est un peu d'API POSIX (pour les parties réseau) et un compilateur C99 (en fait, la plupart des compilateurs non C99 fonctionneront aussi longtemps qu'ils le proposent inttypes.het stdbool.h;gcc deux sont pris en charge bien avant d'offrir un support C99 complet) .

Tout ce que le programme doit exécuter est qu'au moins une interface de votre système (autre que l'interface locale) a une adresse IP attribuée et qu'une route par défaut est définie qui utilise cette interface. Le programme rassemblera cette adresse IP et l'utilisera comme deuxième "adresse spécifique".

Il teste toutes les combinaisons possibles auxquelles vous pouvez penser:

  • Protocole TCP et UDP
  • Sockets normaux, sockets d'écoute (serveur), sockets multicast
  • SO_REUSEADDR défini sur socket1, socket2 ou les deux sockets
  • SO_REUSEPORT défini sur socket1, socket2 ou les deux sockets
  • Toutes les combinaisons d'adresses que vous pouvez créer à partir de 0.0.0.0(caractère générique), 127.0.0.1(adresse spécifique) et de la deuxième adresse spécifique trouvée sur votre interface principale (pour la multidiffusion, c'est juste 224.1.2.3dans tous les tests)

et imprime les résultats dans un joli tableau. Il fonctionnera également sur les systèmes qui ne savent pas SO_REUSEPORT, auquel cas cette option n'est tout simplement pas testée.

Ce que le programme ne peut pas tester facilement, c'est comment SO_REUSEADDRagit sur les socketsTIME_WAIT état car il est très difficile de forcer et de conserver un socket dans cet état. Heureusement, la plupart des systèmes d'exploitation semblent se comporter simplement comme BSD ici et la plupart du temps, les programmeurs peuvent simplement ignorer l'existence de cet état.

Voici le code (je ne peux pas l'inclure ici, les réponses ont une taille limite et le code pousserait cette réponse au-delà de la limite).

Mecki
la source
9
Par exemple, "adresse source" devrait vraiment être "adresse locale", les trois champs suivants également. La liaison avec INADDR_ANYne lie pas les adresses locales existantes, mais aussi toutes les futures. listencrée certainement des sockets avec le même protocole exact, l'adresse locale et le port local, même si vous avez dit que ce n'était pas possible.
Ben Voigt
9
@Ben Source et Destination sont les termes officiels utilisés pour l'adressage IP (auxquels je me réfère principalement). Local et distant n'aurait aucun sens, car l'adresse distante peut en fait être une adresse "locale" et l'opposé de destination est source et non local. Je ne sais pas quel est votre problème INADDR_ANY, je n'ai jamais dit qu'il ne se lierait pas aux adresses futures. Et listenne crée aucune prise, ce qui rend votre phrase un peu étrange.
Mecki
7
@Ben Lorsqu'une nouvelle adresse est ajoutée au système, c'est aussi une "adresse locale existante", elle commence juste à exister. Je ne dis pas « à toutes actuellement les adresses locales existantes ». En fait, je dis même que le socket est en fait vraiment lié au caractère générique , ce qui signifie que le socket est lié à tout ce qui correspond à ce caractère générique, maintenant, demain et dans cent ans. Semblable pour la source et la destination, vous êtes juste en train de tergiverser ici. Avez-vous une réelle contribution technique à apporter?
Mecki
8
@Mecki: Vous pensez vraiment que le mot existant inclut des choses qui n'existent pas maintenant mais qui le seront à l'avenir? La source et la destination ne sont pas un hasard. Lorsque des paquets entrants sont mis en correspondance avec un socket, vous dites que l'adresse de destination dans le paquet sera mise en correspondance avec une adresse "source" du socket? C'est faux et vous le savez, vous avez déjà dit que la source et la destination sont opposées. L' adresse locale sur le socket est comparée à l' adresse de destination des paquets entrants et placée dans l' adresse source sur les paquets sortants.
Ben Voigt
10
@Mecki: Cela a beaucoup plus de sens si vous dites "L'adresse locale du socket est l'adresse source des paquets sortants et l'adresse de destination des paquets entrants". Les paquets ont des adresses source et de destination. Les hôtes et les sockets sur les hôtes ne le font pas. Pour les sockets datagramme, les deux homologues sont égaux. Pour les sockets TCP, en raison de la négociation à trois, il y a un expéditeur (client) et un répondeur (serveur), mais cela ne signifie toujours pas que la connexion ou les sockets connectés ont une source et une destination non plus, car le trafic circule dans les deux sens.
Ben Voigt
1

La réponse de Mecki est absolument parfaite, mais il convient d'ajouter que FreeBSD prend également en charge SO_REUSEPORT_LB, ce qui imite le SO_REUSEPORTcomportement de Linux - il équilibre la charge; voir setsockopt (2)

Edward Tomasz Napierala
la source
Belle découverte. Je ne l'ai pas vu sur les pages de manuel lorsque j'ai vérifié. Cela vaut vraiment la peine d'être mentionné car il peut être très utile lors du portage de logiciels Linux vers FreeBSD.
Mecki