Como SO_REUSEADDR e SO_REUSEPORT diferem?

663

O man pagese documentações programador para as opções de socket SO_REUSEADDRe SO_REUSEPORTsão diferentes para diferentes sistemas operacionais e muitas vezes altamente confuso. Alguns sistemas operacionais nem sequer têm a opção SO_REUSEPORT. A WEB está cheia de informações contraditórias sobre esse assunto e, muitas vezes, você pode encontrar informações verdadeiras apenas para a implementação de um soquete de um sistema operacional específico, que pode nem ser mencionado explicitamente no texto.

Então, como exatamente é SO_REUSEADDRdiferente SO_REUSEPORT?

Os sistemas são SO_REUSEPORTmais limitados?

E qual é exatamente o comportamento esperado se eu usar um deles em diferentes sistemas operacionais?

Mecki
fonte

Respostas:

1616

Bem-vindo ao maravilhoso mundo da portabilidade ... ou melhor, a falta dela. Antes de começarmos a analisar essas duas opções em detalhes e analisar mais detalhadamente como os diferentes sistemas operacionais as tratam, deve-se notar que a implementação do soquete BSD é a mãe de todas as implementações de soquete. Basicamente, todos os outros sistemas copiaram a implementação do soquete BSD em algum momento (ou pelo menos suas interfaces) e começaram a evoluí-la por conta própria. É claro que a implementação do soquete BSD também evoluiu ao mesmo tempo e, portanto, os sistemas que a copiaram mais tarde obtiveram recursos ausentes nos sistemas que a copiaram anteriormente. Compreender a implementação do soquete BSD é a chave para entender todas as outras implementações de soquete; portanto, você deve ler sobre isso, mesmo que não queira escrever código para um sistema BSD.

Existem alguns princípios básicos que você deve saber antes de examinarmos essas duas opções. Uma conexão TCP / UDP é identificada por uma tupla de cinco valores:

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

Qualquer combinação exclusiva desses valores identifica uma conexão. Como resultado, duas conexões não podem ter os mesmos cinco valores; caso contrário, o sistema não poderá mais distingui-las.

O protocolo de um soquete é definido quando um soquete é criado com a socket()função O endereço e a porta de origem são definidos com a bind()função O endereço e a porta de destino são definidos com a connect()função Como o UDP é um protocolo sem conexão, os soquetes UDP podem ser usados ​​sem conectá-los. No entanto, é permitido conectá-los e, em alguns casos, muito vantajoso para o seu código e o design geral do aplicativo. No modo sem conexão, os soquetes UDP que não foram explicitamente vinculados quando os dados são enviados pela primeira vez geralmente são automaticamente vinculados pelo sistema, pois um soquete UDP não vinculado não pode receber nenhum dado (resposta). O mesmo vale para um soquete TCP não vinculado, ele é automaticamente vinculado antes de ser conectado.

Se você ligar explicitamente um soquete, é possível vinculá-lo à porta 0, o que significa "qualquer porta". Como um soquete não pode realmente ser vinculado a todas as portas existentes, nesse caso, o sistema precisará escolher uma porta específica (geralmente de um intervalo predefinido de portas de origem específicas do SO). Existe um curinga semelhante para o endereço de origem, que pode ser "qualquer endereço" ( 0.0.0.0no caso de IPv4 e::no caso de IPv6). Diferentemente do caso de portas, um soquete pode realmente ser vinculado a "qualquer endereço", o que significa "todos os endereços IP de origem de todas as interfaces locais". Se o soquete for conectado posteriormente, o sistema deverá escolher um endereço IP de origem específico, pois um soquete não pode ser conectado e, ao mesmo tempo, ser vinculado a qualquer endereço IP local. Dependendo do endereço de destino e do conteúdo da tabela de roteamento, o sistema selecionará um endereço de origem apropriado e substituirá a ligação "any" por uma ligação ao endereço IP de origem escolhido.

Por padrão, nenhum soquete pode ser vinculado à mesma combinação de endereço e porta de origem. Contanto que a porta de origem seja diferente, o endereço de origem é realmente irrelevante. A ligação socketApara A:Xe socketBpara B:Y, onde Ae Bsão endereços e Xe Ysão portas, sempre é possível enquanto for X != Yverdadeira. No entanto, mesmo que X == Ya ligação ainda seja possível enquanto for A != Bverdadeira. Por exemplo, socketApertence a um programa de servidor FTP e está vinculado 192.168.0.1:21e socketBpertence a outro programa de servidor FTP e está vinculado a 10.0.0.1:21, ambas as ligações terão êxito. Lembre-se, porém, que um soquete pode estar vinculado localmente a "qualquer endereço". Se um soquete estiver vinculado a0.0.0.0:21, ele é vinculado a todos os endereços locais existentes ao mesmo tempo e, nesse caso, nenhum outro soquete pode ser vinculado à porta 21, independentemente de qual endereço IP específico ele tenta se conectar, pois 0.0.0.0conflita com todos os endereços IP locais existentes.

Tudo o que foi dito até agora é praticamente igual para todos os principais sistemas operacionais. As coisas começam a ficar específicas do SO quando a reutilização de endereço entra em ação. Começamos com o BSD, pois como eu disse acima, é a mãe de todas as implementações de soquete.

BSD

SO_REUSEADDR

Se SO_REUSEADDRestiver ativado em um soquete antes de vinculá-lo, o soquete poderá ser vinculado com êxito, a menos que haja um conflito com outro soquete vinculado exatamente à mesma combinação de endereço e porta de origem. Agora você pode se perguntar como isso é diferente do que antes? A palavra-chave é "exatamente". SO_REUSEADDRprincipalmente altera a maneira como os endereços curinga ("qualquer endereço IP") são tratados ao procurar conflitos.

Sem SO_REUSEADDR, de ligação socketAa 0.0.0.0:21e depois a ligação socketBpara 192.168.0.1:21falhará (com erro EADDRINUSE), desde 0.0.0.0 significa "qualquer endereço IP local", portanto, todos os endereços IP locais são considerados em uso por esta tomada e isso inclui 192.168.0.1também. Com SO_REUSEADDRisso, será bem-sucedido, uma vez que 0.0.0.0e não192.168.0.1 é exatamente o mesmo endereço, um é um curinga para todos os endereços locais e o outro é um endereço local muito específico. Observe que a afirmação acima é verdadeira, independentemente da ordem socketAe do socketBvínculo; sem SO_REUSEADDRele sempre falhará, SO_REUSEADDRsempre terá sucesso.

Para oferecer uma visão geral melhor, vamos fazer uma tabela aqui e listar todas as combinações possíveis:

Soquete SO_REUSEADDRUm soqueteB Resultado
-------------------------------------------------- -------------------
  Erro ON / OFF 192.168.0.1:21 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
   Erro OFF 0.0.0.0:21 192.168.1.0:21 (EADDRINUSE)
   Erro OFF 192.168.1.0:21 0.0.0.0:21 (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
  Erro ON / OFF 0.0.0.0:21 0.0.0.0:21 (EADDRINUSE)

A tabela acima pressupõe que socketAjá foi vinculado com êxito ao endereço fornecido e socketA, em seguida, socketBé criado, é SO_REUSEADDRdefinido ou não e, finalmente, é vinculado ao endereço fornecido socketB. Resulté o resultado da operação de ligação para socketB. Se a primeira coluna indicar ON/OFF, o valor de SO_REUSEADDRé irrelevante para o resultado.

Ok, SO_REUSEADDRtem efeito nos endereços curinga, é bom saber. No entanto, esse não é o único efeito que tem. Há outro efeito conhecido, que também é a razão pela qual a maioria das pessoas usa SO_REUSEADDRem programas de servidor em primeiro lugar. Para o outro uso importante dessa opção, precisamos examinar mais detalhadamente como o protocolo TCP funciona.

Um soquete possui um buffer de envio e, se uma chamada para a send()função for bem-sucedida, isso não significa que os dados solicitados foram realmente enviados, apenas significa que os dados foram adicionados ao buffer de envio. Para soquetes UDP, os dados geralmente são enviados muito em breve, se não imediatamente, mas para soquetes TCP, pode haver um atraso relativamente longo entre adicionar dados ao buffer de envio e fazer com que a implementação TCP realmente envie esses dados. Como resultado, quando você fecha um soquete TCP, ainda pode haver dados pendentes no buffer de envio, que ainda não foi enviado, mas seu código considera-o como enviado, pois osend()chamada realizada. Se a implementação do TCP estivesse fechando o soquete imediatamente em sua solicitação, todos esses dados seriam perdidos e seu código nem saberia disso. Diz-se que o TCP é um protocolo confiável e a perda de dados assim não é muito confiável. É por isso que um soquete que ainda tem dados a enviar entrará em um estado chamado TIME_WAITquando você o fechar. Nesse estado, ele aguardará até que todos os dados pendentes tenham sido enviados com sucesso ou até que um tempo limite seja atingido; nesse caso, o soquete será fechado com força.

A quantidade de tempo que o kernel irá esperar antes de ele fecha o socket, independentemente se ele ainda tem dados em voo ou não, é chamado de Tempo Linger . O tempo de espera é globalmente configurável na maioria dos sistemas e, por padrão, bastante longo (dois minutos é um valor comum que você encontrará em muitos sistemas). Também é configurável por soquete usando a opção soquete, SO_LINGERque pode ser usada para diminuir ou diminuir o tempo limite e até mesmo desativá-lo completamente. Desativá-lo completamente é uma péssima idéia, já que fechar um soquete TCP normalmente é um processo um pouco complexo e envolve enviar e voltar alguns pacotes (além de reenviá-los caso eles se percam) e todo esse processo próximo também é limitado pelo Linger Time. Se você desativar a persistência, seu soquete poderá não apenas perder dados em voo, mas também sempre será fechado com força em vez de normalmente, o que geralmente não é recomendado. Os detalhes sobre como uma conexão TCP é fechada normalmente estão além do escopo desta resposta. Se você quiser saber mais, recomendo que você dê uma olhada nesta página . E mesmo que você desabilite a persistência SO_LINGER, se seu processo morrer sem fechar explicitamente o soquete, o BSD (e possivelmente outros sistemas) permanecerá, no entanto, ignorando o que você configurou. Isso acontecerá, por exemplo, se seu código apenas chamarexit()(bastante comum para programas de servidor minúsculos e simples) ou o processo é interrompido por um sinal (que inclui a possibilidade de simplesmente travar devido a um acesso ilegal à memória). Portanto, não há nada que você possa fazer para garantir que um soquete nunca permaneça em todas as circunstâncias.

A questão é: como o sistema trata um soquete no estado TIME_WAIT? Se SO_REUSEADDRnão estiver definido, considera-se que um soquete no estado TIME_WAITainda esteja vinculado ao endereço e porta de origem e qualquer tentativa de vincular um novo soquete ao mesmo endereço e porta falhará até que o soquete seja realmente fechado, o que pode demorar como o tempo de espera configurado . Portanto, não espere que você possa reconectar o endereço de origem de um soquete imediatamente após fechá-lo. Na maioria dos casos, isso irá falhar. No entanto, se SO_REUSEADDRestiver definido para o soquete que você está tentando ligar, outro soquete vinculado ao mesmo endereço e porta no estadoTIME_WAITé simplesmente ignorado, afinal já está "meio morto", e seu soquete pode se conectar exatamente ao mesmo endereço sem nenhum problema. Nesse caso, não desempenha nenhum papel que o outro soquete possa ter exatamente o mesmo endereço e porta. Observe que a ligação de um soquete exatamente ao mesmo endereço e porta que um soquete que está morrendo no TIME_WAITestado pode ter efeitos colaterais inesperados e geralmente indesejados, caso o outro soquete ainda esteja "funcionando", mas isso está além do escopo desta resposta e felizmente esses efeitos colaterais são bastante raros na prática.

Há uma coisa final que você deve saber SO_REUSEADDR. Tudo o que foi escrito acima funcionará desde que o soquete ao qual você deseja se vincular tenha a reutilização de endereço ativada. Não é necessário que o outro soquete, aquele que já esteja vinculado ou esteja em um TIME_WAITestado, também tenha esse sinalizador definido quando foi vinculado. O código que decide se a ligação será bem-sucedida ou falhará apenas inspeciona o SO_REUSEADDRsinalizador do soquete alimentado na bind()chamada; para todos os outros soquetes inspecionados, esse sinalizador nem é analisado.

SO_REUSEPORT

SO_REUSEPORTé o que a maioria das pessoas espera SO_REUSEADDRser. Basicamente, SO_REUSEPORTpermite vincular um número arbitrário de soquetes exatamente ao mesmo endereço e porta de origem, desde que todos os soquetes vinculados anteriores também tenham sido SO_REUSEPORTdefinidos antes de serem vinculados. Se o primeiro soquete vinculado a um endereço e porta não tiver sido SO_REUSEPORTdefinido, nenhum outro soquete poderá ser vinculado exatamente ao mesmo endereço e porta, independentemente se esse outro soquete tiver sido SO_REUSEPORTdefinido ou não, até que o primeiro soquete libere sua ligação novamente. Ao contrário do caso do SO_REUESADDRtratamento de código SO_REUSEPORT, não apenas verificará se o soquete atualmente vinculado foi SO_REUSEPORTdefinido, mas também verificará se o soquete com um endereço e porta conflitantes foi SO_REUSEPORTdefinido quando foi vinculado.

SO_REUSEPORTnão implica SO_REUSEADDR. Isso significa que, se um soquete não tiver sido SO_REUSEPORTdefinido quando foi vinculado e outro somado SO_REUSEPORTquando for vinculado exatamente ao mesmo endereço e porta, a ligação falhará, o que é esperado, mas também falhará se o outro soquete já estiver morrendo e está no TIME_WAITestado. Para poder vincular um soquete aos mesmos endereços e porta que outro soquete no TIME_WAITestado, é necessário SO_REUSEADDRdefinir esse soquete ou SO_REUSEPORTdeve ter sido definido nos dois soquetes antes de vinculá-los. É claro que é permitido definir ambos SO_REUSEPORTe SO_REUSEADDRem um soquete.

Não há muito o que dizer SO_REUSEPORTalém de ter sido adicionado posteriormente SO_REUSEADDR; é por isso que você não o encontrará em muitas implementações de soquete de outros sistemas, que "bifurcaram" o código BSD antes que essa opção fosse adicionada e que não havia maneira de vincular dois soquetes exatamente ao mesmo endereço de soquete no BSD antes desta opção.

Connect () retornando EADDRINUSE?

A maioria das pessoas sabe que bind()pode falhar com o erro EADDRINUSE; no entanto, quando você começa a brincar com a reutilização de endereços, pode se deparar com uma situação estranha que também connect()falha com esse erro. Como isso pode ser? Como um endereço remoto, depois de tudo o que o connect adiciona a um soquete, já está em uso? Conectar vários soquetes exatamente ao mesmo endereço remoto nunca foi um problema antes, então o que está acontecendo de errado aqui?

Como eu disse no topo da minha resposta, uma conexão é definida por uma tupla de cinco valores, lembra? E eu também disse que esses cinco valores devem ser únicos, caso contrário o sistema não poderá mais distinguir duas conexões, certo? Bem, com a reutilização de endereço, você pode vincular dois soquetes do mesmo protocolo ao mesmo endereço e porta de origem. Isso significa que três desses cinco valores já são os mesmos para esses dois soquetes. Se você agora tentar conectar esses dois soquetes também ao mesmo endereço e porta de destino, criaria dois soquetes conectados, cujas tuplas são absolutamente idênticas. Isso não pode funcionar, pelo menos não para conexões TCP (conexões UDP não são conexões reais de qualquer maneira). Se os dados chegassem para uma das duas conexões, o sistema não saberia a qual conexão os dados pertencem.

Portanto, se você vincular dois soquetes do mesmo protocolo ao mesmo endereço e porta de origem e tentar conectá-los ao mesmo endereço e porta de destino, connect()ocorrerá uma falha no erro EADDRINUSEdo segundo soquete que você tentar conectar, o que significa que um O soquete com uma tupla idêntica de cinco valores já está conectado.

Endereços multicast

A maioria das pessoas ignora o fato de existirem endereços multicast, mas eles existem. Enquanto endereços unicast são usados ​​para comunicação um para um, endereços multicast são usados ​​para comunicação um para muitos. A maioria das pessoas ficou sabendo dos endereços multicast ao saber sobre o IPv6, mas os endereços multicast também existiam no IPv4, embora esse recurso nunca tenha sido amplamente utilizado na Internet pública.

O significado das SO_REUSEADDRalterações nos endereços multicast, pois permite que vários soquetes sejam vinculados exatamente à mesma combinação de endereço e porta multicast de origem. Em outras palavras, os endereços multicast se SO_REUSEADDRcomportam exatamente como os SO_REUSEPORTendereços unicast. Na verdade, o código trata SO_REUSEADDRe SO_REUSEPORTidenticamente os endereços multicast, o que significa que você pode dizer que isso SO_REUSEADDRimplica SO_REUSEPORTem todos os endereços multicast e vice-versa.


FreeBSD / OpenBSD / NetBSD

Todos esses são garfos bastante atrasados ​​do código BSD original, é por isso que os três oferecem as mesmas opções que o BSD e também se comportam da mesma forma que no BSD.


macOS (MacOS X)

Em sua essência, o macOS é simplesmente um UNIX estilo BSD chamado " Darwin ", baseado em um fork tardio do código BSD (BSD 4.3), que foi posteriormente sincronizado com o FreeBSD (na época atual) Código 5 para a versão do Mac OS 10.3, para que a Apple possa obter total conformidade com o POSIX (o macOS é certificado pelo POSIX). Apesar de ter um microkernel em seu núcleo (" Mach "), o resto do kernel (" XNU ") é basicamente apenas um kernel BSD, e é por isso que o macOS oferece as mesmas opções que o BSD e também se comporta da mesma maneira que no BSD .

iOS / watchOS / tvOS

O iOS é apenas uma bifurcação do macOS com um kernel ligeiramente modificado e aparado, um pouco despojado do conjunto de ferramentas do espaço do usuário e um conjunto de estruturas padrão ligeiramente diferente. watchOS e tvOS são garfos para iOS, que são ainda mais simples (especialmente watchOS). Que eu saiba, todos eles se comportam exatamente como o macOS.


Linux

Linux <3.9

Antes do Linux 3.9, apenas a opção SO_REUSEADDRexistia. Essa opção geralmente se comporta da mesma forma que no BSD, com duas exceções importantes:

  1. Desde que um soquete TCP de escuta (servidor) esteja vinculado a uma porta específica, a SO_REUSEADDRopção será totalmente ignorada para todos os soquetes direcionados a essa porta. A ligação de um segundo soquete à mesma porta só é possível se isso também foi possível no BSD sem ter SO_REUSEADDRdefinido. Por exemplo, você não pode vincular a um endereço curinga e, em seguida, a um endereço mais específico ou o contrário, ambos são possíveis no BSD se você definir SO_REUSEADDR. O que você pode fazer é ligar a mesma porta e dois endereços diferentes de caracteres não curinga, pois isso sempre é permitido. Nesse aspecto, o Linux é mais restritivo que o BSD.

  2. A segunda exceção é que, para soquetes de cliente, essa opção se comporta exatamente como SO_REUSEPORTno BSD, desde que ambos tenham esse sinalizador definido antes de serem vinculados. A razão para permitir isso era simplesmente o fato de ser importante ligar vários soquetes exatamente ao mesmo endereço de soquete UDP para vários protocolos e, como não havia SO_REUSEPORTantes do 3.9, o comportamento de SO_REUSEADDRfoi alterado para preencher essa lacuna. . Nesse aspecto, o Linux é menos restritivo que o BSD.

Linux> = 3.9

O Linux 3.9 adicionou a opção SO_REUSEPORTao Linux também. Essa opção se comporta exatamente como a opção no BSD e permite a ligação exatamente ao mesmo endereço e número de porta, desde que todos os soquetes tenham essa opção definida antes da ligação.

No entanto, ainda existem duas diferenças SO_REUSEPORTem outros sistemas:

  1. Para evitar o "seqüestro de portas", há uma limitação especial: Todos os soquetes que desejam compartilhar o mesmo endereço e combinação de portas devem pertencer a processos que compartilham o mesmo ID de usuário eficaz! Portanto, um usuário não pode "roubar" portas de outro usuário. Esta é uma mágica especial para compensar um pouco os desaparecidos SO_EXCLBIND/ SO_EXCLUSIVEADDRUSEbandeiras.

  2. Além disso, o kernel executa alguma "mágica especial" para SO_REUSEPORTsoquetes que não é encontrada em outros sistemas operacionais: para soquetes UDP, ele tenta distribuir datagramas uniformemente; para soquetes de escuta TCP, tenta distribuir solicitações de conexão de entrada (aquelas aceitas pela chamada accept()) uniformemente em todos os soquetes que compartilham a mesma combinação de endereço e porta. Assim, um aplicativo pode abrir facilmente a mesma porta em vários processos filhos e usá SO_REUSEPORT-lo para obter um balanceamento de carga muito barato.


Android

Embora todo o sistema Android seja um pouco diferente da maioria das distribuições Linux, em seu núcleo funciona um kernel Linux ligeiramente modificado, portanto, tudo o que se aplica ao Linux também deve se aplicar ao Android.


janelas

O Windows conhece apenas a SO_REUSEADDRopção, não há SO_REUSEPORT. A configuração SO_REUSEADDRem um soquete no Windows se comporta como a configuração SO_REUSEPORTe SO_REUSEADDRem um soquete no BSD, com uma exceção: um soquete com SO_REUSEADDRsempre pode vincular exatamente ao mesmo endereço e porta de origem que um soquete já vinculado, mesmo que o outro soquete não possua essa opção. definido quando foi encadernado . Esse comportamento é um pouco perigoso, pois permite que um aplicativo "roube" a porta conectada de outro aplicativo. Escusado será dizer que isso pode ter grandes implicações de segurança. A Microsoft percebeu que isso poderia ser um problema e, portanto, adicionou outra opção de soquete SO_EXCLUSIVEADDRUSE. ConfiguraçãoSO_EXCLUSIVEADDRUSEem um soquete garante que, se a ligação for bem-sucedida, a combinação de endereço e porta de origem pertencer exclusivamente a esse soquete e nenhum outro soquete poderá se ligar a eles, nem mesmo se tiver sido SO_REUSEADDRdefinido.

Para obter ainda mais detalhes sobre como os sinalizadores SO_REUSEADDRe SO_EXCLUSIVEADDRUSEfuncionam no Windows, como eles influenciam a ligação / re-ligação, a Microsoft gentilmente forneceu uma tabela semelhante à minha tabela perto da parte superior dessa resposta. Basta visitar esta página e rolar um pouco para baixo. Na verdade, existem três tabelas, a primeira mostra o comportamento antigo (anterior ao Windows 2003), a segunda o comportamento (Windows 2003 e posterior) e a terceira mostra como o comportamento muda no Windows 2003 e posterior, se as bind()chamadas são feitas por usuários diferentes.


Solaris

Solaris é o sucessor do SunOS. O SunOS foi originalmente baseado em um fork do BSD, o SunOS 5 e, mais tarde, em um fork do SVR4; no entanto, o SVR4 é uma mesclagem do BSD, System V e Xenix; portanto, até certo ponto, o Solaris também é um fork do BSD e um bastante cedo. Como resultado, Solaris sabe apenas SO_REUSEADDR, não há SO_REUSEPORT. O SO_REUSEADDRcomportamento se comporta da mesma forma que no BSD. Tanto quanto sei, não há como obter o mesmo comportamento SO_REUSEPORTdo Solaris, isso significa que não é possível vincular dois soquetes exatamente ao mesmo endereço e porta.

Semelhante ao Windows, o Solaris tem uma opção para atribuir a um soquete uma ligação exclusiva. Esta opção é nomeada SO_EXCLBIND. Se essa opção for definida em um soquete antes de vinculá-lo, a configuração SO_REUSEADDRem outro soquete não terá efeito se os dois soquetes forem testados quanto a um conflito de endereço. Por exemplo, se socketAestiver vinculado a um endereço curinga e socketBtiver SO_REUSEADDRhabilitado e estiver vinculado a um endereço não curinga e a mesma porta que socketA, essa ligação normalmente será bem-sucedida, a menos que socketAtenha sido SO_EXCLBINDativada, caso em que falhará independentemente do SO_REUSEADDRsinalizador de socketB.


Outros sistemas

Caso seu sistema não esteja listado acima, escrevi um pequeno programa de teste que você pode usar para descobrir como o sistema lida com essas duas opções. Além disso, se você acha que meus resultados estão errados , execute o programa antes de postar comentários e possivelmente fazer falsas alegações.

Tudo o que o código requer para construir é um pouco da API POSIX (para as partes da rede) e um compilador C99 (na verdade, a maioria dos compiladores não-C99 funcionará tão bem quanto eles oferecem inttypes.he stdbool.h, por exemplo, com gccsuporte muito antes de oferecer suporte completo ao C99) .

Tudo o que o programa precisa executar é que pelo menos uma interface em seu sistema (que não seja a interface local) tenha um endereço IP atribuído e que seja definida uma rota padrão que use essa interface. O programa reunirá esse endereço IP e o usará como o segundo "endereço específico".

Ele testa todas as combinações possíveis que você pode imaginar:

  • Protocolo TCP e UDP
  • Soquetes normais, soquetes de escuta (servidor), soquetes multicast
  • SO_REUSEADDR definido no soquete1, soquete2 ou nos dois soquetes
  • SO_REUSEPORT definido no soquete1, soquete2 ou nos dois soquetes
  • Todas as combinações de endereços que você pode criar 0.0.0.0(curinga), 127.0.0.1(endereço específico) e o segundo endereço específico encontrado na interface principal (para multicast, é apenas 224.1.2.3em todos os testes)

e imprime os resultados em uma boa tabela. Também funcionará em sistemas que não sabem SO_REUSEPORT; nesse caso, essa opção simplesmente não é testada.

O que o programa não pode testar facilmente é como SO_REUSEADDRage nos soquetes no TIME_WAITestado, pois é muito difícil forçar e manter um soquete nesse estado. Felizmente, a maioria dos sistemas operacionais parece simplesmente se comportar como o BSD aqui e na maioria das vezes os programadores podem simplesmente ignorar a existência desse estado.

Aqui está o código (não posso incluí-lo aqui, as respostas têm um limite de tamanho e o código levaria essa resposta além do limite).

Mecki
fonte
9
Por exemplo, "endereço de origem" realmente deve ser "endereço local", da mesma forma os próximos três campos. A ligação com INADDR_ANYnão vincula endereços locais existentes, mas todos os futuros também. listencertamente cria soquetes com o mesmo protocolo exato, endereço local e porta local, mesmo que você tenha dito que isso não é possível.
22413 Ben Voigt
9
@Ben Origem e Destino são os termos oficiais usados ​​para o endereçamento IP (aos quais eu me refiro principalmente). Local e Remoto não faria sentido, pois o endereço Remoto pode de fato ser um endereço "Local" e o oposto de Destino é Origem e não Local. Não sei qual é o seu problema INADDR_ANY, nunca disse que não seria vinculado a endereços futuros. E listennão cria nenhum soquete, o que torna sua frase inteira um pouco estranha.
Mecki
7
@ Ben Quando um novo endereço é adicionado ao sistema, ele também é um "endereço local existente", que começou a existir. Eu não disse "para todos os endereços locais existentes no momento ". Na verdade, eu até digo que o soquete está realmente vinculado ao curinga , o que significa que o soquete está vinculado ao que corresponder a esse curinga, agora, amanhã e daqui a cem anos. Semelhante para origem e destino, você está apenas escolhendo aqui. Você tem alguma contribuição técnica real a fazer?
Mecki
8
@Mecki: Você realmente acha que a palavra existente inclui coisas que não existem agora, mas existirão no futuro? A origem e o destino não são um nitpick. Quando pacotes recebidos são correspondidos a um soquete, você está dizendo que o endereço de destino no pacote será comparado com um endereço de "origem" do soquete? Isso está errado e você sabe disso, você já disse que a origem e o destino são opostos. O endereço local no soquete é comparado ao endereço de destino dos pacotes recebidos e colocado no endereço de origem nos pacotes de saída.
precisa
10
@ Mecki: Isso faz muito mais sentido se você disser "O endereço local do soquete é o endereço de origem dos pacotes enviados e o endereço de destino dos pacotes recebidos". Pacotes têm endereços de origem e destino. Hosts e soquetes em hosts, não. Para soquetes de datagrama, os dois pares são iguais. Para soquetes TCP, devido ao handshake de três vias, há um originador (cliente) e um respondedor (servidor), mas isso ainda não significa que a conexão ou soquetes conectados tenham uma origem e um destino , porque o tráfego flui nos dois sentidos.
precisa
1

A resposta de Mecki é absolutamente perfeita, mas vale a pena acrescentar que o FreeBSD também suporta SO_REUSEPORT_LB, o que imita o SO_REUSEPORTcomportamento do Linux - ele equilibra a carga; veja setsockopt (2)

Edward Tomasz Napierala
fonte
Boa descoberta. Não vi isso nas páginas de manual quando verifiquei. Definitivamente vale a pena mencionar, pois pode ser muito útil ao transportar o software Linux para o FreeBSD.
Mecki