Quando a opção TCP SO_LINGER (0) é necessária?

95

Acho que entendo o significado formal da opção. Em algum código legado que estou lidando agora, a opção é usada. O cliente reclama do RST como resposta ao FIN de seu lado na conexão próxima de seu lado.

Não tenho certeza se posso removê-lo com segurança, pois não entendo quando ele deve ser usado.

Você pode dar um exemplo de quando a opção seria necessária?

dimba
fonte
1
Você deve removê-lo. Não deve ser usado em código de produção. A única vez que o vi usado foi como resultado de um benchmark inválido.
Marquês de Lorne

Respostas:

82

O motivo típico para definir um SO_LINGERtempo limite de zero é evitar um grande número de conexões paradas no TIME_WAITestado, ocupando todos os recursos disponíveis em um servidor.

Quando uma conexão TCP é fechada de forma limpa, o fim que iniciou o fechamento ("fechamento ativo") termina com a conexão parada TIME_WAITpor vários minutos. Portanto, se o seu protocolo for aquele em que o servidor inicia o fechamento da conexão e envolve um grande número de conexões de curta duração, ele pode ser suscetível a esse problema.

Esta não é uma boa idéia, porém - TIME_WAITexiste por uma razão (para garantir que pacotes perdidos de conexões antigas não interfiram com as novas). É uma ideia melhor redesenhar seu protocolo para um em que o cliente inicie o fechamento da conexão, se possível.

caf
fonte
3
Eu concordo totalmente. Eu vi um aplicativo de monitoramento que estava iniciando muitas (alguns milhares de conexões de curta duração a cada X segundos) e tinha problemas para aumentar (mais mil conexões). Não sei por que, mas o aplicativo não estava respondendo. Alguém sugeriu SO_LINGER = true, TIME_WAIT = 0 para liberar recursos do sistema operacional rapidamente e, após uma breve investigação, tentamos essa solução com resultados muito bons. O TIME_WAIT não é mais um problema para este aplicativo.
bartosz.r
24
Discordo. Um protocolo de nível de aplicativo sobre o TCP deve ser projetado de forma que o cliente sempre inicie o fechamento da conexão. Dessa forma, TIME_WAITo cliente ficará sentado diante do cliente sem causar danos. Lembre-se do que diz em "Programação de rede UNIX" terceira edição (Stevens et al), página 203: "O estado TIME_WAIT é seu amigo e está lá para nos ajudar. Em vez de tentar evitar o estado, devemos entendê-lo (Seção 2.7) . "
mgd
8
E se um cliente quiser abrir 4.000 conexões a cada 30 segundos (este aplicativo de monitoramento é um cliente! Porque ele inicia a conexão)? Sim, podemos redesenhar o aplicativo, adicionar alguns agentes locais na infraestrutura, alterar o modelo para push. Mas se já tivermos esse aplicativo e ele crescer, podemos fazê-lo funcionar ajustando twe linger. Você altera um parâmetro e, de repente, tem o aplicativo funcionando, sem investir um orçamento para implementar a nova arquitetura.
bartosz.r
3
@ bartosz.r: Só estou dizendo que usar SO_LINGER com timeout 0 deve ser o último recurso. Novamente, em "Programação de rede UNIX" terceira edição (Stevens et al) página 203 também diz que você corre o risco de corrupção de dados. Considere a leitura do RFC 1337, onde você pode ver por que TIME_WAIT é seu amigo.
mgd
7
@caf Não, a solução clássica seria um pool de conexão, como visto em todas as APIs TCP de serviço pesado, por exemplo HTTP 1.1.
Marquês de Lorne
188

Para minha sugestão, por favor leia a última seção: “Quando usar SO_LINGER com tempo limite 0” .

Antes de entrarmos nisso, uma pequena palestra sobre:

  • Terminação TCP normal
  • TIME_WAIT
  • FIN, ACKeRST

Terminação TCP normal

A sequência normal de terminação do TCP é semelhante a esta (simplificada):

Temos dois pares: A e B

  1. A chama close()
    • A envia FINpara B
    • A entra no FIN_WAIT_1estado
  2. B recebe FIN
    • B envia ACKpara A
    • B entra no CLOSE_WAITestado
  3. A recebe ACK
    • A entra no FIN_WAIT_2estado
  4. B chama close()
    • B envia FINpara A
    • B entra no LAST_ACKestado
  5. A recebe FIN
    • A envia ACKpara B
    • A entra no TIME_WAITestado
  6. B recebe ACK
    • B vai para o CLOSEDestado - ou seja, é removido das tabelas de soquete

TEMPO DE ESPERA

Portanto, o par que inicia a terminação - ou seja, as chamadas close()primeiro - terminará no TIME_WAITestado.

Para entender por que o TIME_WAITestado é nosso amigo, leia a seção 2.7 em "Programação de rede UNIX" terceira edição de Stevens et al (página 43).

No entanto, pode ser um problema com muitos soquetes em TIME_WAITestado em um servidor, pois pode impedir que novas conexões sejam aceitas.

Para contornar esse problema, tenho visto muitos sugerindo definir a opção de soquete SO_LINGER com tempo limite 0 antes de chamar close(). No entanto, essa é uma solução ruim, pois faz com que a conexão TCP seja encerrada com um erro.

Em vez disso, projete o protocolo do aplicativo de forma que o encerramento da conexão seja sempre iniciado no lado do cliente. Se o cliente sempre souber quando leu todos os dados restantes, ele pode iniciar a sequência de encerramento. Por exemplo, um navegador sabe pelo Content-Lengthcabeçalho HTTP quando leu todos os dados e pode iniciar o fechamento. (Eu sei que no HTTP 1.1 ele vai mantê-lo aberto por um tempo para uma possível reutilização, e então fechá-lo.)

Se o servidor precisar fechar a conexão, projete o protocolo do aplicativo de forma que o servidor peça ao cliente para ligar close().

Quando usar SO_LINGER com tempo limite 0

Mais uma vez, de acordo com a terceira edição da página 202-203 de "Programação de rede UNIX", definir SO_LINGERcom o tempo limite 0 antes da chamada close()fará com que a sequência de encerramento normal não seja iniciada.

Em vez disso, o par configurando esta opção e chamando close()enviará um RST(reset de conexão) que indica uma condição de erro e é assim que será percebida na outra extremidade. Normalmente, você verá erros como "Conexão redefinida por par".

Portanto, na situação normal, é realmente uma má ideia definir SO_LINGERcom o tempo limite 0 antes de chamar close()- a partir de agora chamado de fechamento abortivo - em um aplicativo de servidor.

No entanto, certas situações justificam isso de qualquer maneira:

  • Se o cliente de seu aplicativo de servidor se comportar mal (tempo limite, retornar dados inválidos, etc.), um fechamento abortivo faz sentido para evitar ficar preso CLOSE_WAITou terminar no TIME_WAITestado.
  • Se você deve reiniciar seu aplicativo de servidor que atualmente tem milhares de conexões de cliente, você pode considerar definir esta opção de soquete para evitar milhares de soquetes de servidor TIME_WAIT(ao chamar close()do lado do servidor), pois isso pode impedir que o servidor obtenha portas disponíveis para novas conexões de cliente após ser reiniciado.
  • Na página 202 do livro mencionado, ele diz especificamente: "Existem certas circunstâncias que justificam o uso deste recurso para enviar um fechamento abortivo. Um exemplo é um servidor de terminal RS-232, que pode travar para sempre ao CLOSE_WAITtentar entregar dados a um terminal travado porta, mas redefiniria corretamente a porta travada se conseguisse um RSTpara descartar os dados pendentes. "

Eu recomendaria este longo artigo, que acredito dar uma resposta muito boa à sua pergunta.

mgd
fonte
6
TIME_WAITé um amigo somente quando não começa a causar problemas: stackoverflow.com/questions/1803566/…
Pacerier
2
e daí se você estiver escrevendo um servidor web? como você "diz ao cliente para iniciar um fechamento"?
Shaun Neal
2
@ShaunNeal você obviamente não sabe. Mas um cliente / navegador bem escrito iniciará o fechamento. Se o cliente não estiver se comportando bem, felizmente temos o assassinato de TIME_WAIT para garantir que não fiquemos sem descritores de soquete e portas efêmeras.
mgd
16

Quando o linger está ativado, mas o tempo limite é zero, a pilha TCP não espera que os dados pendentes sejam enviados antes de fechar a conexão. Os dados podem ser perdidos devido a isso, mas ao definir linger dessa maneira, você está aceitando isso e pedindo que a conexão seja redefinida imediatamente em vez de encerrada normalmente. Isso faz com que um RST seja enviado em vez do FIN usual.

Agradecimentos ao EJP pelo comentário, veja aqui mais detalhes.

Len Holgate
fonte
1
Eu entendo isso. o que estou pedindo é um exemplo "realista" de quando gostaríamos de usar o hard reset.
dimba de
5
Sempre que você quiser abortar uma conexão; então, se o seu protocolo falhar na validação e você tiver um cliente falando mal de você de repente, você abortaria a conexão com um RST, etc.
Len Holgate
5
Você está confundindo tempo limite de demora zero com tempo de espera desligado. Linger off significa que close () não bloqueia. Linger on com um timeout positivo significa que close () bloqueia até o timeout. Prolongue-se com um tempo limite zero causa RST, e é disso que se trata.
Marquês de Lorne
2
Sim, você está correto. Vou ajustar a resposta para corrigir minha terminologia.
Len Holgate
6

Se você pode remover o linger em seu código com segurança ou não depende do tipo de seu aplicativo: é um "cliente" (abrindo conexões TCP e fechando-as ativamente primeiro) ou é um "servidor" (ouvindo um TCP aberto e fechá-lo após o outro lado iniciar o fechamento)?

Se seu aplicativo tem o sabor de um "cliente" (fechando primeiro) E você inicia e fecha um grande número de conexões a diferentes servidores (por exemplo, quando seu aplicativo é um aplicativo de monitoramento supervisionando a acessibilidade de um grande número de servidores diferentes) seu aplicativo tem o problema de que todas as suas conexões de cliente estão travadas no estado TIME_WAIT. Em seguida, eu recomendaria reduzir o tempo limite para um valor menor do que o padrão para ainda desligar normalmente, mas liberar os recursos de conexões do cliente mais cedo. Eu não definiria o tempo limite como 0, pois 0 não encerra normalmente com FIN, mas aborta com RST.

Se a sua aplicação tem o sabor de um "cliente" e precisa buscar uma grande quantidade de pequenos arquivos do mesmo servidor, você não deve iniciar uma nova conexão TCP por arquivo e acabar em uma grande quantidade de conexões de cliente em TIME_WAIT, mas mantenha a conexão aberta e busque todos os dados na mesma conexão. A opção Linger pode e deve ser removida.

Se a sua aplicação for um “servidor” (fechar segundo como reação ao fechamento do par), em close () sua conexão é desligada normalmente e os recursos são liberados porque você não entra no estado TIME_WAIT. Linger não deve ser usado. Mas se o seu aplicativo de servidor possui um processo de supervisão que detecta conexões abertas inativas que ficam ociosas por um longo tempo ("longo" deve ser definido), você pode desligar essa conexão inativa do seu lado - veja isso como um tipo de tratamento de erro - com um desligamento abortivo. Isso é feito definindo o tempo limite de linger para 0. close () irá então enviar um RST para o cliente, dizendo a ele que você está bravo :-)

Grandswiss
fonte
1

Em servidores, você pode querer enviar em RSTvez de FINao desconectar clientes com comportamento inadequado. Isso pula FIN-WAITseguido por TIME-WAITestados de soquete no servidor, o que evita o esgotamento dos recursos do servidor e, portanto, protege contra esse tipo de ataque de negação de serviço.

Maxim Egorushkin
fonte
0

Gosto da observação de Maxim de que os ataques DOS podem exaurir os recursos do servidor. Isso também acontece sem um adversário realmente malicioso.

Alguns servidores têm que lidar com o 'ataque DOS não intencional' que ocorre quando o aplicativo cliente tem um bug com vazamento de conexão, onde eles continuam criando uma nova conexão para cada novo comando que enviam ao seu servidor. E então, talvez, eventualmente, fechando suas conexões se atingirem a pressão do GC, ou talvez as conexões acabem se esgotando.

Outro cenário é quando 'todos os clientes têm o mesmo endereço TCP'. Então, as conexões do cliente são distinguíveis apenas por números de porta (se conectarem a um único servidor). E se os clientes começarem a abrir / fechar conexões rapidamente por qualquer motivo, eles podem esgotar o espaço de tupla (endereço do cliente + porta, IP do servidor + porta).

Portanto, acho que os servidores podem ser mais aconselhados a mudar para a estratégia Linger-Zero quando virem um grande número de soquetes no estado TIME_WAIT - embora isso não corrija o comportamento do cliente, pode reduzir o impacto.

Tim Lovell-Smith
fonte
0

O soquete de escuta em um servidor pode usar linger com tempo 0 para ter acesso à ligação de volta ao soquete imediatamente e para redefinir quaisquer clientes cujas conexões ainda não tenham terminado de conectar. TIME_WAIT é algo que só é interessante quando você tem uma rede de múltiplos caminhos e pode acabar com pacotes mal ordenados ou de outra forma está lidando com pedidos / tempos de chegada de pacotes de rede estranhos.

Gregg Wonderly
fonte