In cosa differiscono SO_REUSEADDR e SO_REUSEPORT?


663

L' man pagese documentazioni programmatore per le opzioni di socket SO_REUSEADDRe SO_REUSEPORTsono diversi per i diversi sistemi operativi e spesso altamente confuso. Alcuni sistemi operativi non hanno nemmeno l'opzione SO_REUSEPORT. Il WEB è pieno di informazioni contraddittorie su questo argomento e spesso puoi trovare informazioni che sono vere solo per l'implementazione di un socket di un sistema operativo specifico, che potrebbe anche non essere esplicitamente menzionata nel testo.

Quindi, come è esattamente SO_REUSEADDRdiverso da SO_REUSEPORT?

I sistemi sono SO_REUSEPORTpiù limitati?

E qual è esattamente il comportamento previsto se uso uno su sistemi operativi diversi?

Risposte:


1617

Benvenuti nel meraviglioso mondo della portabilità ... o meglio della sua mancanza. Prima di iniziare ad analizzare in dettaglio queste due opzioni e dare uno sguardo più approfondito al modo in cui i diversi sistemi operativi le gestiscono, va notato che l'implementazione del socket BSD è la madre di tutte le implementazioni del socket. Fondamentalmente tutti gli altri sistemi hanno copiato l'implementazione del socket BSD ad un certo punto nel tempo (o almeno le sue interfacce) e poi hanno iniziato ad evolverlo da soli. Naturalmente anche l'implementazione del socket BSD si è evoluta allo stesso tempo e quindi i sistemi che l'hanno copiata in seguito hanno caratteristiche che mancavano nei sistemi che l'avevano copiato in precedenza. Comprendere l'implementazione del socket BSD è la chiave per comprendere tutte le altre implementazioni del socket, quindi dovresti leggere a riguardo anche se non ti interessa scrivere mai codice per un sistema BSD.

Ci sono un paio di nozioni di base che dovresti conoscere prima di esaminare queste due opzioni. Una connessione TCP / UDP è identificata da una tupla di cinque valori:

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

Qualsiasi combinazione univoca di questi valori identifica una connessione. Di conseguenza, due connessioni non possono avere gli stessi cinque valori, altrimenti il ​​sistema non sarebbe più in grado di distinguerle.

Il protocollo di un socket viene impostato quando viene creato un socket con la socket()funzione. L'indirizzo e la porta di origine sono impostati con la bind()funzione. L'indirizzo e la porta di destinazione sono impostati con la connect()funzione. Poiché UDP è un protocollo senza connessione, i socket UDP possono essere utilizzati senza collegarli. Tuttavia, è consentito collegarli e in alcuni casi è molto vantaggioso per il codice e la progettazione generale dell'applicazione. In modalità senza connessione, i socket UDP che non sono stati esplicitamente associati quando i dati vengono inviati per la prima volta su di essi sono normalmente associati automaticamente dal sistema, poiché un socket UDP non associato non può ricevere alcun dato (risposta). Lo stesso vale per un socket TCP non associato, viene automaticamente associato prima di essere collegato.

Se si associa esplicitamente un socket, è possibile associarlo alla porta 0, che significa "qualsiasi porta". Dal momento che un socket non può realmente essere associato a tutte le porte esistenti, il sistema dovrà scegliere una porta specifica stessa in quel caso (di solito da un intervallo predefinito di porte di origine specifiche del sistema operativo). Esiste un carattere jolly simile per l'indirizzo di origine, che può essere "qualsiasi indirizzo" ( 0.0.0.0nel caso di IPv4 e::in caso di IPv6). A differenza del caso delle porte, un socket può davvero essere associato a "qualsiasi indirizzo" che significa "tutti gli indirizzi IP di origine di tutte le interfacce locali". Se il socket viene collegato in un secondo momento, il sistema deve scegliere un indirizzo IP di origine specifico, poiché un socket non può essere collegato e allo stesso tempo essere associato a qualsiasi indirizzo IP locale. A seconda dell'indirizzo di destinazione e del contenuto della tabella di routing, il sistema sceglierà un indirizzo di origine appropriato e sostituirà l'associazione "qualsiasi" con un'associazione all'indirizzo IP di origine selezionato.

Per impostazione predefinita, non è possibile associare due socket alla stessa combinazione di indirizzo e porta di origine. Finché la porta di origine è diversa, l'indirizzo di origine è effettivamente irrilevante. Il collegamento socketAa A:Xe socketBa B:Y, dove Ae Bsono indirizzi e Xe Ysono porte, è sempre possibile purché sia X != Yvero. Tuttavia, anche se X == Y, l'associazione è ancora possibile purché sia A != Bvera. Ad esempio, socketAappartiene a un programma server FTP ed è associato 192.168.0.1:21e socketBappartiene a un altro programma server FTP ed è associato 10.0.0.1:21, entrambi i collegamenti avranno esito positivo. Tieni presente, tuttavia, che un socket può essere localmente associato a "qualsiasi indirizzo". Se è associato un socket0.0.0.0:21, è associato a tutti gli indirizzi locali esistenti contemporaneamente e in tal caso nessun altro socket può essere associato alla porta 21, indipendentemente dall'indirizzo IP specifico a cui tenta di collegarsi, poiché è in 0.0.0.0conflitto con tutti gli indirizzi IP locali esistenti.

Tutto ciò che è stato detto finora è praticamente uguale per tutti i principali sistemi operativi. Le cose iniziano a diventare specifiche del sistema operativo quando entra in gioco il riutilizzo degli indirizzi. Iniziamo con BSD, poiché come ho detto sopra, è la madre di tutte le implementazioni di socket.

BSD

SO_REUSEADDR

Se SO_REUSEADDRè abilitato su un socket prima di associarlo, il socket può essere associato correttamente a meno che non vi sia un conflitto con un altro socket associato esattamente alla stessa combinazione di indirizzo e porta di origine. Ora potresti chiederti come è diverso da prima? La parola chiave è "esattamente". SO_REUSEADDRcambia principalmente il modo in cui gli indirizzi jolly ("qualsiasi indirizzo IP") vengono trattati durante la ricerca di conflitti.

Senza SO_REUSEADDR, vincolante socketAper 0.0.0.0:21poi vincolante socketBper 192.168.0.1:21fallirà (con errore EADDRINUSE), poiché 0.0.0.0 significa "qualsiasi indirizzo IP locale", così tutti gli indirizzi IP locali sono considerati in uso da questa presa e questo include 192.168.0.1, anche. Con SO_REUSEADDResso avrà successo, poiché 0.0.0.0e non192.168.0.1 sono esattamente lo stesso indirizzo, uno è un carattere jolly per tutti gli indirizzi locali e l'altro è un indirizzo locale molto specifico. Si noti che la precedente affermazione è vera indipendentemente dall'ordine socketAe dal socketBlimite; senza di SO_REUSEADDRessa fallirà sempre, con SO_REUSEADDResso avrà sempre successo.

Per darti una panoramica migliore, facciamo una tabella qui ed elenchiamo tutte le possibili combinazioni:

SO_REUSEADDR socketA socketB Risultato
-------------------------------------------------- -------------------
  ON / OFF 192.168.0.1:21 Errore 192.168.0.1:21 (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 Errore (EADDRINUSE)
   OFF 192.168.1.0:21 0.0.0.0:21 Errore (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 Errore (EADDRINUSE)

La tabella sopra presuppone che socketAsia già stato associato correttamente all'indirizzo indicato socketA, quindi socketBviene creato, viene SO_REUSEADDRimpostato o meno e infine viene associato all'indirizzo indicato socketB. Resultè il risultato dell'operazione di associazione per socketB. Se la prima colonna dice ON/OFF, il valore di SO_REUSEADDRè irrilevante per il risultato.

Va bene, SO_REUSEADDRha un effetto sugli indirizzi jolly, buono a sapersi. Eppure non è solo l'effetto che ha. C'è un altro effetto ben noto che è anche il motivo per cui la maggior parte delle persone utilizza SO_REUSEADDRin primo luogo i programmi server. Per l'altro importante uso di questa opzione dobbiamo dare uno sguardo più approfondito al funzionamento del protocollo TCP.

Un socket ha un buffer di invio e se una chiamata alla send()funzione ha esito positivo, ciò non significa che i dati richiesti siano stati effettivamente inviati, significa solo che i dati sono stati aggiunti al buffer di invio. Per i socket UDP, i dati vengono in genere inviati molto presto, se non immediatamente, ma per i socket TCP, può esserci un ritardo relativamente lungo tra l'aggiunta di dati al buffer di invio e l'implementazione TCP che li invia realmente. Di conseguenza, quando si chiude un socket TCP, potrebbero esserci ancora dati in sospeso nel buffer di invio, che non è stato ancora inviato ma il codice lo considera inviato, poichésend()chiamata riuscita. Se l'implementazione TCP chiudesse immediatamente il socket sulla tua richiesta, tutti questi dati andrebbero persi e il tuo codice non lo saprebbe nemmeno. Si dice che TCP sia un protocollo affidabile e perdere dati in questo modo non è molto affidabile. Ecco perché un socket che ha ancora dati da inviare andrà in uno stato chiamato TIME_WAITquando lo si chiude. In quello stato attenderà fino a quando tutti i dati in sospeso non saranno stati inviati correttamente o fino a quando non verrà raggiunto un timeout, nel qual caso il socket verrà chiuso forzatamente.

Il tempo che il kernel attenderà prima di chiudere il socket, indipendentemente dal fatto che abbia ancora dati in volo o meno, è chiamato Linger Time . Il Linger Time è configurabile a livello globale sulla maggior parte dei sistemi e per impostazione predefinita è piuttosto lungo (due minuti è un valore comune che troverai su molti sistemi). È inoltre configurabile per socket utilizzando l'opzione socket SO_LINGERche può essere utilizzata per ridurre o prolungare il timeout e persino per disabilitarlo completamente. Disabilitarlo completamente è una pessima idea, dal momento che chiudere un socket TCP con grazia è un processo leggermente complesso e comporta l'invio e il ritorno di un paio di pacchetti (oltre a rispedire quei pacchetti nel caso in cui si perdessero) e l'intero processo di chiusura è anche limitato dal Linger Time. Se disabiliti il ​​persistere, il tuo socket potrebbe non solo perdere dati in volo, ma sarà anche sempre chiuso forzatamente anziché con garbo, cosa che di solito non è raccomandata. I dettagli su come una connessione TCP viene chiusa con grazia vanno oltre lo scopo di questa risposta, se vuoi saperne di più, ti consiglio di dare un'occhiata a questa pagina . E anche se hai disabilitato di indugiare SO_LINGER, se il tuo processo si interrompe senza chiudere esplicitamente il socket, BSD (e possibilmente altri sistemi) rimarrà comunque, ignorando ciò che hai configurato. Questo accadrà ad esempio se il tuo codice chiama e bastaexit()(abbastanza comune per programmi server minuscoli e semplici) o il processo viene interrotto da un segnale (che include la possibilità che si blocchi semplicemente a causa di un accesso illegale alla memoria). Quindi non c'è niente che tu possa fare per assicurarti che un socket non indugi mai in tutte le circostanze.

La domanda è: in che modo il sistema tratta un socket nello stato TIME_WAIT? Se SO_REUSEADDRnon impostato, TIME_WAITsi considera che un socket in stato sia ancora associato all'indirizzo e alla porta di origine e qualsiasi tentativo di associare un nuovo socket allo stesso indirizzo e porta fallirà fino a quando il socket non sarà stato realmente chiuso, il che potrebbe richiedere del tempo come Linger Time configurato . Quindi non aspettarti di poter ricollegare l'indirizzo di origine di un socket subito dopo averlo chiuso. Nella maggior parte dei casi questo fallirà. Tuttavia, se SO_REUSEADDRè impostato per il socket che si sta tentando di associare, un altro socket è associato allo stesso indirizzo e porta nello statoTIME_WAITviene semplicemente ignorato, dopo tutto è già "mezzo morto", e il tuo socket può legarsi esattamente allo stesso indirizzo senza alcun problema. In tal caso, non ha alcun ruolo il fatto che l'altro socket possa avere esattamente lo stesso indirizzo e porta. Si noti che l'associazione di un socket esattamente allo stesso indirizzo e porta di un socket morente nello TIME_WAITstato può avere effetti collaterali imprevisti, e di solito indesiderati, nel caso in cui l'altro socket sia ancora "al lavoro", ma questo va oltre lo scopo di questa risposta e fortunatamente questi effetti collaterali sono piuttosto rari nella pratica.

C'è un'ultima cosa che dovresti sapere SO_REUSEADDR. Tutto quanto scritto sopra funzionerà finché il socket a cui vuoi associare ha il riutilizzo dell'indirizzo abilitato. Non è necessario che l'altro socket, quello che è già associato o sia in uno TIME_WAITstato, abbia anch'esso impostato questo flag quando era associato. Il codice che decide se il bind avrà esito positivo o negativo controlla solo il SO_REUSEADDRflag del socket inserito nella bind()chiamata, per tutti gli altri socket controllati, questo flag non viene nemmeno esaminato.

SO_REUSEPORT

SO_REUSEPORTè ciò che la maggior parte delle persone si aspetterebbe SO_REUSEADDRdi essere. Fondamentalmente, SO_REUSEPORTconsente di associare un numero arbitrario di socket esattamente allo stesso indirizzo e porta di origine, purché anche tutti i socket associati SO_REUSEPORTprecedenti siano stati impostati prima che fossero associati. Se il primo socket associato a un indirizzo e una porta non è SO_REUSEPORTimpostato, nessun altro socket può essere associato esattamente allo stesso indirizzo e porta, indipendentemente dal fatto che l'altro socket sia SO_REUSEPORTimpostato o meno, fino a quando il primo socket non rilascerà nuovamente il suo binding. A differenza del caso in cui SO_REUESADDRla gestione del codice SO_REUSEPORTnon solo verificherà che il socket attualmente associato sia SO_REUSEPORTimpostato, ma verificherà anche che il socket con un indirizzo e una porta in conflitto era SO_REUSEPORTimpostato quando era associato.

SO_REUSEPORTnon implica SO_REUSEADDR. Ciò significa che se un socket non è stato SO_REUSEPORTimpostato quando è stato associato e un altro socket è stato SO_REUSEPORTimpostato quando è associato esattamente allo stesso indirizzo e porta, il bind non riesce, come previsto, ma fallisce anche se l'altro socket sta già morendo e è nello TIME_WAITstato. Per poter associare un socket agli stessi indirizzi e alla stessa porta di un altro socket nello TIME_WAITstato, è necessario che sia SO_REUSEADDRimpostato su quel socket o che SO_REUSEPORTsia stato impostato su entrambi i socket prima di collegarli. Ovviamente è consentito impostare entrambi SO_REUSEPORTe SO_REUSEADDR, su un socket.

Non c'è molto altro da dire SO_REUSEPORTa parte che è stato aggiunto più tardi SO_REUSEADDR, ecco perché non lo troverai in molte implementazioni socket di altri sistemi, che hanno "biforcato" il codice BSD prima che questa opzione fosse aggiunta, e che non ci fosse modo di associare due socket esattamente allo stesso indirizzo socket in BSD prima di questa opzione.

Connect () Restituisce EADDRINUSE?

Molte persone sanno che bind()potrebbe non riuscire con l'errore EADDRINUSE, tuttavia, quando inizi a giocare con il riutilizzo degli indirizzi, potresti imbatterti nella strana situazione che connect()fallisce anche con quell'errore. Come può essere? Come può un indirizzo remoto, dopo tutto ciò che Connect aggiunge a un socket, essere già in uso? Collegare più socket esattamente allo stesso indirizzo remoto non è mai stato un problema prima, quindi cosa c'è che non va?

Come ho detto all'inizio della mia risposta, una connessione è definita da una tupla di cinque valori, ricordi? E ho anche detto che questi cinque valori devono essere univoci, altrimenti il ​​sistema non può più distinguere due connessioni, giusto? Bene, con il riutilizzo degli indirizzi, è possibile associare due socket dello stesso protocollo allo stesso indirizzo e porta di origine. Ciò significa che tre di questi cinque valori sono già gli stessi per questi due socket. Se ora provi a connettere entrambi questi socket anche allo stesso indirizzo e porta di destinazione, creeresti due socket connessi, le cui tuple sono assolutamente identiche. Questo non può funzionare, almeno non per le connessioni TCP (le connessioni UDP non sono comunque connessioni reali). Se i dati sono arrivati ​​per una delle due connessioni, il sistema non è in grado di dire a quale connessione appartengono i dati.

Pertanto, se si associano due socket dello stesso protocollo allo stesso indirizzo e porta di origine e si tenta di collegarli entrambi allo stesso indirizzo e porta di destinazione, connect()in realtà non riuscirà con l'errore EADDRINUSEper il secondo socket che si tenta di connettersi, il che significa che un socket con una tupla identica di cinque valori è già collegato.

Indirizzi multicast

Molte persone ignorano il fatto che esistano indirizzi multicast, ma esistono. Mentre gli indirizzi unicast vengono utilizzati per le comunicazioni one-to-one, gli indirizzi multicast vengono utilizzati per le comunicazioni one-to-many. La maggior parte delle persone è venuta a conoscenza degli indirizzi multicast quando ha appreso dell'IPv6, ma gli indirizzi multicast esistevano anche in IPv4, anche se questa funzionalità non è mai stata ampiamente utilizzata su Internet pubblica.

Il significato delle SO_REUSEADDRmodifiche per gli indirizzi multicast in quanto consente a più socket di essere associati esattamente alla stessa combinazione di indirizzo e porta multicast di origine. In altre parole, per gli indirizzi multicast SO_REUSEADDRsi comporta esattamente come SO_REUSEPORTper gli indirizzi unicast. In realtà, il codice tratta SO_REUSEADDRe SO_REUSEPORTidenticamente per gli indirizzi multicast, ciò significa che si potrebbe dire che SO_REUSEADDRimplica SO_REUSEPORTtutti gli indirizzi multicast e viceversa.


FreeBSD / OpenBSD / NetBSD

Tutte queste sono forcelle piuttosto recenti del codice BSD originale, ecco perché offrono tutte e tre le stesse opzioni di BSD e si comportano allo stesso modo di BSD.


macOS (MacOS X)

Fondamentalmente, macOS è semplicemente un UNIX in stile BSD chiamato " Darwin ", basato su un fork piuttosto recente del codice BSD (BSD 4.3), che in seguito è stato anche risincronizzato con il FreeBSD (a quel tempo attuale) Base di codice 5 per la versione Mac OS 10.3, in modo che Apple potesse ottenere la piena conformità POSIX (macOS è certificato POSIX). Nonostante abbia un microkernel nel suo nucleo (" Mach "), il resto del kernel (" XNU ") è fondamentalmente solo un kernel BSD, ed è per questo che macOS offre le stesse opzioni di BSD e si comportano allo stesso modo di BSD .

iOS / watchOS / tvOS

iOS è solo un fork di macOS con un kernel leggermente modificato e rifinito, un po 'ridotto set di strumenti per lo spazio utente e un set di framework predefinito leggermente diverso. watchOS e tvOS sono forcelle iOS, ulteriormente ridotte (in particolare watchOS). Per quanto ne so, tutti si comportano esattamente come macOS.


Linux

Linux <3.9

Prima di Linux 3.9, SO_REUSEADDResisteva solo l'opzione . Questa opzione si comporta generalmente come in BSD con due importanti eccezioni:

  1. Finché un socket TCP in ascolto (server) è associato a una porta specifica, l' SO_REUSEADDRopzione viene completamente ignorata per tutti i socket destinati a tale porta. Associare un secondo socket alla stessa porta è possibile solo se era possibile anche in BSD senza averlo SO_REUSEADDRimpostato. Ad esempio, non è possibile eseguire il binding a un indirizzo jolly e quindi a uno più specifico o viceversa, entrambi sono possibili in BSD se impostato SO_REUSEADDR. Quello che puoi fare è associarti alla stessa porta e a due diversi indirizzi non jolly, come sempre consentito. Sotto questo aspetto Linux è più restrittivo di BSD.

  2. La seconda eccezione è che per i socket client, questa opzione si comporta esattamente come SO_REUSEPORTin BSD, a condizione che entrambi abbiano impostato questo flag prima di essere associati. Il motivo per cui era consentito era semplicemente che era importante essere in grado di associare più socket esattamente allo stesso indirizzo socket UDP per vari protocolli e, poiché non esisteva SO_REUSEPORTprima del 3.9, il comportamento di è SO_REUSEADDRstato modificato di conseguenza per colmare tale lacuna . Sotto questo aspetto Linux è meno restrittivo di BSD.

Linux> = 3.9

Linux 3.9 ha aggiunto l'opzione anche SO_REUSEPORTa Linux. Questa opzione si comporta esattamente come l'opzione in BSD e consente l'associazione esattamente allo stesso indirizzo e numero di porta purché tutti i socket abbiano questa opzione impostata prima di associarli.

Tuttavia, ci sono ancora due differenze rispetto SO_REUSEPORTad altri sistemi:

  1. Per impedire il "dirottamento delle porte", esiste una limitazione speciale: tutti i socket che desiderano condividere lo stesso indirizzo e la stessa combinazione di porte devono appartenere a processi che condividono lo stesso ID utente effettivo! Quindi un utente non può "rubare" le porte di un altro utente. Questa è una magia speciale per compensare in qualche modo la mancanza SO_EXCLBIND/ SO_EXCLUSIVEADDRUSEbandiere.

  2. Inoltre il kernel esegue un po 'di "magia speciale" per i SO_REUSEPORTsocket che non si trovano in altri sistemi operativi: per i socket UDP, tenta di distribuire i datagrammi in modo uniforme, per i socket di ascolto TCP, tenta di distribuire le richieste di connessione in entrata (quelle accettate chiamando accept()) uniformemente su tutti i socket che condividono la stessa combinazione di indirizzo e porta. Pertanto un'applicazione può facilmente aprire la stessa porta in più processi figlio e quindi utilizzare SO_REUSEPORTper ottenere un bilanciamento del carico molto economico.


androide

Anche se l'intero sistema Android è in qualche modo diverso dalla maggior parte delle distribuzioni Linux, al suo interno funziona un kernel Linux leggermente modificato, quindi tutto ciò che si applica a Linux dovrebbe applicarsi anche ad Android.


finestre

Windows conosce solo l' SO_REUSEADDRopzione, non esiste SO_REUSEPORT. L'impostazione SO_REUSEADDRsu un socket in Windows si comporta come impostazione SO_REUSEPORTe SO_REUSEADDRsu un socket in BSD, con un'eccezione: un socket con SO_REUSEADDRpuò sempre associarsi esattamente allo stesso indirizzo e porta di origine di un socket già associato, anche se l'altro socket non aveva questa opzione impostato quando era legato . Questo comportamento è alquanto pericoloso perché consente a un'applicazione di "rubare" la porta connessa di un'altra applicazione. Inutile dire che ciò può avere importanti implicazioni per la sicurezza. Microsoft ha capito che questo potrebbe essere un problema e ha quindi aggiunto un'altra opzione socket SO_EXCLUSIVEADDRUSE. AmbientazioneSO_EXCLUSIVEADDRUSEsu un socket si assicura che se l'associazione ha esito positivo, la combinazione di indirizzo di origine e porta è di proprietà esclusiva di questo socket e nessun altro socket può collegarsi ad essi, anche se è SO_REUSEADDRimpostato.

Per ulteriori dettagli su come i flag SO_REUSEADDRe il SO_EXCLUSIVEADDRUSEfunzionamento su Windows, su come influenzano l'associazione / la rilegatura, Microsoft ha gentilmente fornito una tabella simile alla mia tabella nella parte superiore di quella risposta. Basta visitare questa pagina e scorrere un po 'verso il basso. In realtà ci sono tre tabelle, la prima mostra il vecchio comportamento (prima di Windows 2003), la seconda il comportamento (da Windows 2003 in poi) e la terza mostra come cambia il comportamento in Windows 2003 e successivamente se le bind()chiamate vengono effettuate da utenti diversi.


Solaris

Solaris è il successore di SunOS. SunOS era originariamente basato su un fork di BSD, SunOS 5 e successivamente era basato su un fork di SVR4, tuttavia SVR4 è una fusione di BSD, System V e Xenix, quindi fino a un certo punto Solaris è anche un fork di BSD, e un piuttosto presto. Di conseguenza Solaris lo sa solo SO_REUSEADDR, non esiste SO_REUSEPORT. Il SO_REUSEADDRcomportamento è più o meno lo stesso di BSD. Per quanto ne so non c'è modo di ottenere lo stesso comportamento SO_REUSEPORTdi Solaris, ciò significa che non è possibile associare due socket esattamente allo stesso indirizzo e porta.

Simile a Windows, Solaris ha un'opzione per assegnare a un socket un'associazione esclusiva. Questa opzione è denominata SO_EXCLBIND. Se questa opzione è impostata su un socket prima di collegarlo, l'impostazione SO_REUSEADDRsu un altro socket non ha alcun effetto se i due socket vengono testati per un conflitto di indirizzi. Ad esempio, se socketAè associato a un indirizzo jolly e socketBha SO_REUSEADDRabilitato ed è associato a un indirizzo non jolly e alla stessa porta di socketAquesto collegamento normalmente avrà esito positivo, a meno che non socketAsia stato SO_EXCLBINDabilitato, nel qual caso non riuscirà a prescindere dalla SO_REUSEADDRbandiera di socketB.


Altri sistemi

Nel caso in cui il tuo sistema non sia elencato sopra, ho scritto un piccolo programma di test che puoi usare per scoprire come il tuo sistema gestisce queste due opzioni. Inoltre, se ritieni che i miei risultati siano errati , esegui prima quel programma prima di pubblicare qualsiasi commento e possibilmente fare affermazioni false.

Tutto ciò che il codice richiede per costruire è un po 'di API POSIX (per le parti di rete) e un compilatore C99 (in realtà la maggior parte dei compilatori non C99 funzionerà tanto quanto offrono inttypes.he stdbool.h; ad esempio gccsupportati entrambi molto prima di offrire il pieno supporto C99) .

Tutto ciò che il programma deve eseguire è che almeno un'interfaccia nel tuo sistema (diversa dall'interfaccia locale) abbia un indirizzo IP assegnato e che sia impostata una route predefinita che utilizza quell'interfaccia. Il programma raccoglierà quell'indirizzo IP e lo utilizzerà come secondo "indirizzo specifico".

Verifica tutte le possibili combinazioni a cui puoi pensare:

  • Protocollo TCP e UDP
  • Prese normali, prese di ascolto (server), prese multicast
  • SO_REUSEADDR impostato su socket1, socket2 o entrambi
  • SO_REUSEPORT impostato su socket1, socket2 o entrambi
  • Tutte le combinazioni di indirizzi che è possibile creare 0.0.0.0(carattere jolly), 127.0.0.1(indirizzo specifico) e il secondo indirizzo specifico trovato nell'interfaccia principale (per il multicast è solo 224.1.2.3in tutti i test)

e stampa i risultati in una bella tabella. Funzionerà anche su sistemi che non conoscono SO_REUSEPORT, nel qual caso questa opzione non è semplicemente testata.

Ciò che il programma non può facilmente testare è come SO_REUSEADDRagisce sui socket nello TIME_WAITstato in quanto è molto difficile forzare e mantenere un socket in quello stato. Fortunatamente la maggior parte dei sistemi operativi sembra semplicemente comportarsi come BSD qui e la maggior parte delle volte i programmatori possono semplicemente ignorare l'esistenza di quello stato.

Ecco il codice (non posso includerlo qui, le risposte hanno un limite di dimensioni e il codice spingerebbe questa risposta oltre il limite).


9
Ad esempio, "indirizzo di origine" in realtà dovrebbe essere "indirizzo locale", allo stesso modo i tre campi successivi. Associare con INADDR_ANYnon associa gli indirizzi locali esistenti, ma anche quelli futuri. listencrea sicuramente socket con lo stesso protocollo, indirizzo locale e porta locale identici, anche se hai detto che non è possibile.
Ben Voigt,

9
@Ben Source e Destination sono i termini ufficiali utilizzati per l'indirizzamento IP (a cui mi riferisco principalmente). Local e Remote non avrebbe alcun senso, dal momento che l'indirizzo Remote può in effetti essere un indirizzo "Local" e l'opposto di Destination è Source e non Local. Non so quale sia il tuo problema INADDR_ANY, non ho mai detto che non si legherebbe a indirizzi futuri. E listennon crea alcun socket, il che rende l'intera frase un po 'strana.
Mecki,

7
@Ben Quando un nuovo indirizzo viene aggiunto al sistema, è anche un "indirizzo locale esistente", appena iniziato a esistere. Non ho detto "a tutti gli indirizzi locali attualmente esistenti". In realtà dico anche che il socket è in realtà legato al jolly , il che significa che il socket è legato a qualunque cosa corrisponda a questo jolly, ora, domani e tra cento anni. Simile per fonte e destinazione, stai solo facendo un pignolo qui. Hai qualche reale contributo tecnico da dare?
Mecki,

8
@Mecki: Pensi davvero che la parola esistente includa cose che non esistono ora ma lo faranno in futuro? La fonte e la destinazione non sono un gioco da ragazzi. Quando i pacchetti in arrivo vengono abbinati a un socket, stai dicendo che l'indirizzo di destinazione nel pacchetto verrà confrontato con un indirizzo "sorgente" del socket? È sbagliato e lo sai, hai già detto che la fonte e la destinazione sono opposti. L' indirizzo locale sul socket viene confrontato con l' indirizzo di destinazione dei pacchetti in entrata e inserito nell'indirizzo di origine sui pacchetti in uscita.
Ben Voigt,

10
@Mecki: Questo ha molto più senso se dici "L'indirizzo locale del socket è l'indirizzo di origine dei pacchetti in uscita e l'indirizzo di destinazione dei pacchetti in entrata". I pacchetti hanno indirizzi di origine e destinazione. Host e socket su host non lo fanno. Per i socket dei datagrammi entrambi i peer sono uguali. Per i socket TCP, a causa dell'handshake a tre vie, c'è un originator (client) e un responder (server), ma ciò non significa che la connessione o i socket connessi abbiano anche una sorgente e una destinazione , perché il traffico scorre in entrambe le direzioni.
Ben Voigt,

1

La risposta di Mecki è assolutamente perfetta, ma vale la pena aggiungere che supporta anche FreeBSD SO_REUSEPORT_LB, che imita il SO_REUSEPORTcomportamento di Linux : bilancia il carico; vedi setsockopt (2)


Bella scoperta. Non l'ho visto nelle pagine man quando ho controllato. Vale sicuramente la pena di essere menzionato in quanto può essere molto utile quando si esegue il porting di software Linux su FreeBSD.
Mecki
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.