El reenvío de puerto remoto SSH falló

27

Seguimiento: Parece que la serie rápida de desconexiones que coinciden con unos pocos meses de ejecución de cada servidor probablemente sea una coincidencia y solo sirvió para revelar el problema real. La razón por la que no se pudo reconectar es casi seguro debido a los valores de AliveInterval (respuesta de kasperd). El uso de la opción ExitOnForwardFailure debería permitir que el tiempo de espera se produzca correctamente antes de volver a conectar, lo que debería resolver el problema en la mayoría de los casos. La sugerencia de MadHatter (el script de eliminación) es probablemente la mejor manera de asegurarse de que el túnel pueda volver a conectarse incluso si todo lo demás falla.

Tengo un servidor (A) detrás de un firewall que inicia un túnel inverso en varios puertos a un pequeño DigitalOcean VPS (B) para poder conectarme a A a través de la dirección IP de B. El túnel ha estado funcionando de manera constante durante aproximadamente 3 meses, pero de repente ha fallado cuatro veces en las últimas 24 horas. Lo mismo sucedió hace un tiempo en otro proveedor de VPS: meses de funcionamiento perfecto, y de repente múltiples fallas rápidas.

Tengo un script en la máquina A que ejecuta automáticamente el comando del túnel ( ssh -R *:X:localhost:X address_of_Bpara cada puerto X) pero cuando se ejecuta, dice Warning: remote port forwarding failed for listen port X.

Entrar en el sshd /var/log/secureen el servidor muestra estos errores:

bind: Address already in use
error: bind: Address already in use
error: channel_setup_fwd_listener: cannot listen to port: X

Resolver requiere reiniciar el VPS. Hasta entonces, todos los intentos de reconexión dan el mensaje "error de reenvío de puerto remoto" y no funcionarán. Ahora es el punto donde el túnel solo dura aproximadamente 4 horas antes de detenerse.

Nada ha cambiado en el VPS, y es una máquina de un solo uso y un solo usuario que solo sirve como punto final del túnel inverso. Está ejecutando OpenSSH_5.3p1 en CentOS 6.5. Parece que sshd no está cerrando los puertos cuando se pierde la conexión. No puedo explicar por qué, o por qué sucedería repentinamente ahora después de meses de operación casi perfecta.

Para aclarar, primero necesito entender por qué sshd se niega a escuchar en los puertos después de que falla el túnel, lo que parece ser causado por sshd que deja los puertos abiertos y nunca los cierra. Ese parece ser el principal problema. Simplemente no estoy seguro de qué haría que se comportara de esta manera después de meses de comportarse como esperaba (es decir, cerrar los puertos de inmediato y permitir que el script se vuelva a conectar).

Justin Mrkva
fuente
¿Cuál es tu pregunta? ¿Cómo abordar el error de enlace del puerto, o cómo averiguar por qué ssh está muriendo, o algo más otra vez?
MadHatter apoya a Monica
Necesito averiguar por qué sshd se niega a abrir los puertos en el VPS (el error de enlace). El error de enlace de puerto parece ser la raíz del problema, y ​​todo debería funcionar si puedo resolverlo.
Justin Mrkva
2
Para cualquier acechador tardío, en lugar de crear manualmente una secuencia de comandos para mantener abierta la conexión, simplemente use autossh en su lugar, que lo hace por usted. serverfault.com/questions/598210/…
oligofren

Respuestas:

28

Estoy de acuerdo con MadHatter, que es probable que se trate de reenvíos de puertos desde conexiones ssh difuntas. Incluso si su problema actual resulta ser otra cosa, puede esperar encontrarse con esas conexiones ssh obsoletas tarde o temprano.

Hay tres formas en que pueden ocurrir conexiones inactivas:

  • Uno de los dos puntos finales se reinició mientras que el otro extremo de la conexión estaba completamente inactivo.
  • Uno de los dos puntos finales cerró la conexión, pero en el momento en que se cerró la conexión, hubo una interrupción temporal en la conexión. La interrupción duró unos minutos después de que se cerró la conexión y, por lo tanto, el otro extremo nunca se enteró de la conexión cerrada.
  • La conexión sigue siendo completamente funcional en ambos puntos finales de la conexión ssh, pero alguien ha colocado un dispositivo con estado en algún lugar entre ellos, que agotó el tiempo de espera debido a la inactividad. Este dispositivo con estado sería un NAT o un firewall, el firewall que ya mencionó es el principal sospechoso.

Averiguar cuál de los tres anteriores está sucediendo no es muy importante, porque hay un método que abordará los tres. Ese es el uso de mensajes keepalive.

Debe buscar la ClientAliveIntervalpalabra clave sshd_configy el ServerAliveIntervalintervalo para ssh_configo ~/.ssh/config.

Ejecutar el sshcomando en un bucle puede funcionar bien. También es una buena idea insertar un modo de suspensión en el bucle para que no termine inundando el servidor cuando la conexión falla por algún motivo.

Si el cliente se vuelve a conectar antes de que la conexión haya finalizado en el servidor, puede terminar en una situación en la que la nueva conexión ssh está activa, pero no tiene reenvíos de puertos. Para evitar eso, debe usar la ExitOnForwardFailurepalabra clave en el lado del cliente.

kasperd
fuente
Estoy pensando que este puede ser el problema. En particular, mi secuencia de comandos en A intentará volver a conectarse a B si el proceso ssh muere (por supuesto, dado que el mensaje de advertencia no mata el proceso ssh, simplemente se cuelga cuando esto sucede, pero eso es un problema para otro día). Pero si A intenta reconectarse con B demasiado rápido, B puede estar esperando que A se reconecte. Probablemente necesito asegurarme de que B siempre agote el tiempo de espera antes de que A vuelva a conectarse. La combinación de eso con la sugerencia de MadHatter de eliminar los procesos sshd antes de volver a conectarse probablemente cubrirá el 95% de los posibles casos.
Justin Mrkva
1
Y hablando del mensaje de advertencia de no matar a SSH, eso me hizo pensar ... y mirar páginas de manual. Resulta que -o ExitOnForwardFailure yeses exactamente lo que necesitaba. Así que esa es una cosa menos que necesito resolver. Para pensar, iba a escribir un script de Python para analizar esos mensajes de advertencia. Esto es mucho más simple. : D
Justin Mrkva
Perdón por olvidarme ExitOnForwardFailureal escribir mi respuesta. Lo he agregado a la respuesta ahora.
kasperd
44
No hay problema, y ​​en realidad lo fue -o ExitOnForwardFailure=yes(tenga en cuenta el signo igual). Entonces, si alguien se encuentra con esto, no copie y pegue de mi comentario anterior, no funcionará. : P
Justin Mrkva
Así que he estado monitoreando el servidor durante aproximadamente 10 horas y parece que está funcionando bien; Asumo en este punto que esta respuesta es correcta (estoy aproximadamente 99% seguro de lo que he visto) y que la serie de desconexiones rápidas fue una coincidencia relacionada con problemas de red que aparecieron unos meses después comenzando cada servicio. Gracias a todos por vuestra ayuda. ;)
Justin Mrkva
4

Puede encontrar el proceso que vincula el puerto en ese servidor con

sudo netstat -apn|grep -w X

Parece muy probable que sea medio difunto sshd, pero ¿por qué hacer suposiciones cuando puede tener datos? También es una buena manera para que un script encuentre un PID para enviar la señal 9 antes de intentar abrir el túnel nuevamente.

MadHatter apoya a Monica
fuente
Recuerdo haber comprobado eso en el proveedor de VPS anterior y confirmó que sshd era el proceso de escuchar esos puertos. La próxima vez que ocurra lo comprobaré aquí, pero como el comportamiento y la configuración son exactamente los mismos, no espero que sea diferente.
Justin Mrkva
Genial, así que haga que su script que vuelve a abrir el túnel mate al viejo túnel antes de intentar hacerlo.
MadHatter apoya a Monica
Nunca hay más de un script de túnel (en A) ejecutándose a la vez, si eso es lo que estás diciendo. Por otro lado, si quiere que el script ejecute de forma remota un comando en B para eliminar los procesos perdidos ... en realidad no es una mala idea. Pero una preocupación es eliminar repetidamente todas las conexiones SSH si estoy tratando de depurar. Si el guión en A siempre está matando a B debido a un problema técnico, entonces no puedo ser constantemente expulsado de B por el pícaro guión A. : P Tendré que probar para asegurarme de que no haga eso. Pero como dije, no es una mala idea. ;)
Justin Mrkva
No pensé que la hubiera. Dices que hay un script ejecutándose en el servidor remoto que intenta abrir un túnel y falla, debido al error de enlace, y supongo que solo se ejecuta cuando lo necesitas (es decir, cuando el túnel existente no es bueno) porque no has dicho lo contrario. Todo lo que sugiero es que elimine el proceso específico que mantiene el puerto abierto antes de que intente abrir el nuevo túnel.
MadHatter apoya a Monica
El script que ejecuta ssh solo está en el servidor A, el servidor B es un servidor simple sin scripts adicionales. Lo que probablemente haré es escribir un script de interrupción para poner en el servidor B, luego llamarlo de forma remota desde A si no se conecta una cierta cantidad de veces seguidas. De esa manera, es menos probable que interfiera con otras conexiones SSH. Y probablemente tendré el registro del script kill cada vez que se ejecute y salga sin hacer nada si se llama demasiadas veces demasiado rápido. Personalmente, parece que limitar la velocidad de cualquier script que mate a sshd probablemente sea prudente. : P
Justin Mrkva
3

Para mí, cuando un sshtúnel se desconecta, la conexión tarda un tiempo en restablecerse, por lo que el sshproceso continúa bloqueándose, dejándome sin túneles activos y no sé por qué. Una solución alternativa es poner sshen segundo plano -fy generar nuevas conexiones sin esperar a que se restablezcan las conexiones antiguas. Se -o ExitOnForwardFailure=yespuede utilizar para limitar el número de nuevos procesos. El -o ServerAliveInterval=60mejora la fiabilidad de su conexión actual.

Se puede repetir el sshcomando de frecuencia, por ejemplo, en una cron, o, en un bucle en el script, por ejemplo, en la siguiente, corremos el sshcomando de cada 3 minutos:

while (1)
do
    ssh -f user@hostname -Rport:host:hostport -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=60
    sleep 180
done
Stephen Quan
fuente
una solución mucho más robusta sería usar autossh
Marco Lavagnino
-o ExitOnForwardFailure=yesera lo que estaba buscando, muchas gracias!
vadipp
1

En mi experiencia, ssh tiene el hábito un poco molesto de no salir limpiamente si 'algo' todavía se está ejecutando en el sistema remoto. Por ejemplo, comenzó en segundo plano. Puedes reproducir esto de la siguiente manera:

ssh <server>
while true; do  sleep 60; done&
exit

Su ssh cerrará sesión, pero en realidad no cerrará la sesión, hasta que finalice el proceso remoto (lo cual no ocurrirá, porque es un bucle 'while true'). Puede que esté sucediendo algo similar: su sesión tiene un proceso 'atascado' que está siendo generado por ssh. El puerto permanece en uso y, por lo tanto, no puede ser reutilizado por su proceso local.

Sobrique
fuente
El comando SSH completo que se ejecuta en la máquina A es ssh -o ConnectTimeout=10 -o BatchMode=yes -gnN -R *:X:localhost:X root@$TUNSRV 1>>tunnel.log 2>&1 &para que SSH no ejecute nada excepto el túnel en sí, específicamente debido a la opción -N. Todo lo que se mantiene abierto se realiza en el servidor remoto B utilizando sshd.
Justin Mrkva