Determinar el puerto asignado dinámicamente para OpenSSH RemoteForward

Determinar el puerto asignado dinámicamente para OpenSSH RemoteForward

Pregunta (TL;DR)

Al asignar puertos dinámicamente para el reenvío remoto (también conocido como -Ropción), ¿cómo puede un script en la máquina remota (por ejemplo, procedente de .bashrc) determinar qué puertos fueron elegidos por OpenSSH?


Fondo

Utilizo OpenSSH (en ambos extremos) para conectarme a nuestro servidor central, que comparto con muchos otros usuarios. Para mi sesión remota (por ahora) me gustaría reenviar X, cups y pulseaudio.

El más trivial es reenviar X, usando la -Xopción. La dirección X asignada se almacena en la variable ambiental DISPLAYy a partir de ahí puedo determinar el puerto TCP correspondiente, en la mayoría de los casos de todos modos. Pero casi nunca lo necesito, porque Xlib respeta DISPLAY.

Necesito un mecanismo similar para tazas y pulseaudio. Los conceptos básicos para ambos servicios existen, en forma de variables ambientales CUPS_SERVERy PULSE_SERVER, respectivamente. A continuación se muestran ejemplos de uso:

ssh -X -R12345:localhost:631 -R54321:localhost:4713 datserver

export CUPS_SERVER=localhost:12345
lowriter #and I can print using my local printer
lpr -P default -o Duplex=DuplexNoTumble minutes.pdf #printing through the tunnel
lpr -H localhost:631 -P default -o Duplex=DuplexNoTumble minutes.pdf #printing remotely

mpg123 mp3s/van_halen/jump.mp3 #annoy co-workers
PULSE_SERVER=localhost:54321 mpg123 mp3s/van_halen/jump.mp3 #listen to music through the tunnel

El problema es configurarlo CUPS_SERVERcorrectamente PULSE_SERVER.

Usamos mucho el reenvío de puertos y, por lo tanto, necesito asignaciones de puertos dinámicas. Las asignaciones de puertos estáticos no son una opción.

OpenSSH tiene un mecanismo para la asignación dinámica de puertos en el servidor remoto, especificando 0como puerto de enlace para el reenvío remoto (la -Ropción). Al utilizar el siguiente comando, OpenSSH asignará dinámicamente puertos para tazas y reenvío de pulsos.

ssh -X -R0:localhost:631 -R0:localhost:4713 datserver

Cuando uso ese comando, sshimprimiré lo siguiente en STDERR:

Allocated port 55710 for remote forward to 127.0.0.1:4713
Allocated port 41273 for remote forward to 127.0.0.1:631

¡Ahí está la información que quiero! En última instancia quiero generar:

export CUPS_SERVER=localhost:41273
export PULSE_SERVER=localhost:55710

Sin embargo, los mensajes "Puerto asignado ..." se crean en mi máquina local y se envían a STDERR, a la que no puedo acceder en la máquina remota. Por extraño que parezca, OpenSSH no parece tener medios para recuperar información sobre reenvíos de puertos.

¿Cómo obtengo esa información para colocarla en un script de shell para configurarla adecuadamente CUPS_SERVERen PULSE_SERVERel host remoto?


Callejones sin salida

Lo único fácil que pude encontrar fue aumentar la detalle sshdhasta que esa información pueda leerse en los registros. Esto no es viable ya que esa información revela mucha más información de la que sería sensato hacer accesible a usuarios no root.

Estaba pensando en parchear OpenSSH para admitir una secuencia de escape adicional que imprima una buena representación de la estructura interna permitted_opens, pero incluso si eso es lo que quiero, todavía no puedo acceder a las secuencias de escape del cliente desde el lado del servidor.


Debe haber una mejor manera

El siguiente enfoque parece muy inestable y está limitado a una sesión SSH por usuario. Sin embargo, necesito al menos dos sesiones simultáneas y otros usuarios aún más. Pero lo intenté...

Cuando las estrellas están alineadas correctamente, después de haber sacrificado uno o dos pollos, puedo abusar del hecho de que sshdno se inicia como mi usuario, pero pierde privilegios después de iniciar sesión exitosamente, para hacer esto:

  • obtener una lista de números de puerto para todos los sockets de escucha que pertenecen a mi usuario

    netstat -tlpen | grep ${UID} | sed -e 's/^.*:\([0-9]\+\) .*$/\1/'

  • obtener una lista de números de puerto para todos los sockets de escucha que pertenecen a los procesos que inició mi usuario

    lsof -u ${UID} 2>/dev/null | grep LISTEN | sed -e 's/.*:\([0-9]\+\) (LISTEN).*$/\1/'

  • Todos los puertos que están en el primer conjunto, pero no en el segundo, tienen una alta probabilidad de ser mis puertos de reenvío y, de hecho, al restar los conjuntos se obtiene 41273, 55710y 6010; tazas, pulso y X, respectivamente.

  • 6010se identifica como el puerto X usando DISPLAY.

  • 41273es el puerto de tazas, porque lpstat -h localhost:41273 -aregresa 0.
  • 55710es el puerto de pulso, porque pactl -s localhost:55710 statdevuelve 0. (¡Incluso imprime el nombre de host de mi cliente!)

(Para hacer la resta establecida I sort -uy almacenar el resultado de las líneas de comando anteriores y usar commpara hacer la resta).

Pulseaudio me permite identificar al cliente y, para todos los efectos, esto puede servir como ancla para separar las sesiones SSH que necesitan separarse. Sin embargo, no he encontrado una manera de vincularlo 41273al 55710mismo 6010proceso sshd. netstatno revelará esa información a usuarios que no sean root. Solo aparece un -en la PID/Program namecolumna donde me gustaría leer 2339/54(en este caso particular). Tan cerca ...

Respuesta1

Lamentablemente no encontré tu pregunta antes, pero acabo de recibir una muy buena respuesta de kamil-maciorowski:

https://unix.stackexchange.com/a/584505/251179

En resumen, primero establezca una conexión maestra, manténgala en segundo plano y luego emita un segundo comando con -O *ctl_cmd*set to forwardpara solicitar/configurar el reenvío de puertos:

ssh -fNMS /path/to/socket user@server

port="$(ssh -S /path/to/socket -O forward -R 0:localhost:22 placeholder)"

Esto le permitirá acceder $porta su máquina local y a una conexión en segundo plano.

Luego puedes usarlo $portlocalmente; o utilícelo sshnuevamente para ejecutar un comando en el servidor remoto, donde puede usar el mismo socket de control.

En cuanto a las banderas, estas son:

  • -F= Solicita ssh para pasar al fondo.
  • -NORTE= No ejecutar un comando remoto.
  • -METRO= Coloca al cliente en modo "maestro", para compartir conexión.
  • -S= Ubicación de una toma de control para compartir conexión.
  • -O= Controlar un proceso maestro de multiplexación de conexión activa.

En mi caso he añadido un poco más para seguir comprobando la conexión:

#!/bin/bash

#--------------------------------------------------
# Setup
#--------------------------------------------------

  set -u;

  tunnel_user="user";
  tunnel_host="1.1.1.1";

  local_port="22";
  local_name="my-name";

  path_key="$HOME/.ssh/tunnel_ed25519";

  path_lock="/tmp/tunnel.${tunnel_host}.pid"
  path_port="/tmp/tunnel.${tunnel_host}.port"
  path_log="/tmp/tunnel.${tunnel_host}.log"
  path_socket="/tmp/tunnel.${tunnel_host}.socket"

#--------------------------------------------------
# Key file
#--------------------------------------------------

  if [ ! -f "${path_key}" ]; then

    ssh-keygen -q -t ed25519 -f "${path_key}" -N "";

    /usr/local/bin/tunnel-client-key.sh
      # Sends the public key to a central server, also run via cron, so it can be added to ~/.ssh/authorized_keys
      # curl -s --form-string "pass=${pass}" --form-string "name=$(local_name)" -F "key=@${path_key}.pub" "https://example.com/key/";

  fi

#--------------------------------------------------
# Lock
#--------------------------------------------------

  if [ -e "${path_lock}" ]; then
    c=$(pgrep -F "${path_lock}" 2>/dev/null | wc -l);
      # MacOS 10.15.4 does not support "-c" to count processes, or the full "--pidfile" flag.
  else
    c=0;
  fi

  if [[ "${c}" -gt 0 ]]; then
    if tty -s; then
      echo "Already running";
    fi;
    exit;
  fi;

  echo "$$" > "${path_lock}";

#--------------------------------------------------
# Port forward
#--------------------------------------------------

  retry=0;

  while true; do

    #--------------------------------------------------
    # Log cleanup
    #--------------------------------------------------

      if [ ! -f "${path_log}" ]; then
        touch "${path_log}";
      fi

      tail -n 30 "${path_log}" > "${path_log}.tmp";

      mv "${path_log}.tmp" "${path_log}";

    #--------------------------------------------------
    # Exit old sockets
    #--------------------------------------------------

      if [ -S "${path_socket}" ]; then

        echo "$(date) : Exit" >> "${path_log}";

        ssh -S "${path_socket}" -O exit placeholder;

      fi

    #--------------------------------------------------
    # Lost lock
    #--------------------------------------------------

      if [ ! -e "${path_lock}" ] || ! grep -q "$$" "${path_lock}"; then

        echo "$(date) : Lost Lock" >> "${path_log}";

        exit;

      fi

    #--------------------------------------------------
    # Master connection
    #--------------------------------------------------

      echo "$(date) : Connect ${retry}" >> "${path_log}";

      ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -fNTMS "${path_socket}" -i "${path_key}" "${tunnel_user}@${tunnel_host}" >> "${path_log}" 2>&1;

    #--------------------------------------------------
    # Setup and keep checking the port forwarding
    #--------------------------------------------------

      old_port=0;

      while ssh -S "${path_socket}" -O check placeholder 2>/dev/null; do

        new_port=$(ssh -S "${path_socket}" -O forward -R "0:localhost:${local_port}" placeholder 2>&1);

        if [[ "${new_port}" -gt 0 ]]; then

          retry=0;

          if [[ "${new_port}" -ne "${old_port}" ]]; then

            ssh -i "${path_key}" "${tunnel_user}@${tunnel_host}" "tunnel.port.sh '${new_port}' '${local_name}'" >> "${path_log}" 2>&1;
              # Tell remote server what the port is, and local_name.
              # Don't use socket, it used "-N"; if done, a lost connection keeps sshd running on the remote host, even with ClientAliveInterval/ClientAliveCountMax.

            echo "$(date) : ${new_port}" >> "${path_log}";

            echo "${new_port}" > "${path_port}";

            old_port="${new_port}";

            sleep 1;

          else

            sleep 300; # Looks good, check again in 5 minutes.

          fi

        else # Not a valid port number (0, empty string, number followed by an error message, etc?)

          ssh -S "${path_socket}" -O exit placeholder 2>/dev/null;

        fi

      done

    #--------------------------------------------------
    # Cleanup
    #--------------------------------------------------

      if [ ! -f "${path_port}" ]; then
        rm "${path_port}";
      fi

      echo "$(date) : Disconnected" >> "${path_log}";

    #--------------------------------------------------
    # Delay before next re-try
    #--------------------------------------------------

      retry=$((retry+1));

      if [[ $retry -gt 10 ]]; then
        sleep 180; # Too many connection failures, try again in 3 minutes
      else
        sleep 5;
      fi

  done

Respuesta2

Logré lo mismo creando una tubería en el cliente local y luego redirigiendo stderr a la tubería que también se redirige a la entrada de ssh. No se requieren múltiples conexiones ssh para suponer que hay un puerto conocido libre que podría fallar. De esta manera, el banner de inicio de sesión y el texto "Puerto asignado ### ..." se redirigen al host remoto.

Tengo un script simple en el host getsshport.shque se ejecuta en el host remoto y que lee la entrada redirigida y analiza el puerto. Mientras este script no finalice, el reenvío remoto ssh permanece abierto.

lado local

mkfifo pipe
ssh -R "*:0:localhost:22" user@remotehost "~/getsshport.sh" 3>&1 1>&2 2>&3 < pipe | cat > pipe

3>&1 1>&2 2>&3es un pequeño truco para intercambiar stderr y stdout, de modo que stderr se canalice a cat y toda la salida normal de ssh se muestre en stderr.

lado remoto ~/getsshport.sh

#!/bin/sh
echo "Connection from $SSH_CLIENT"
while read line
do
    echo "$line" # echos everything sent back to the client
    echo "$line" | sed -n "s/Allocated port \([0-9]*\) for remote forward to \(.*\)\:\([0-9]*\).*/client port \3 is on local port \1/p" >> /tmp/allocatedports
done

Primero intenté recibir grepel mensaje de "puerto asignado" en el lado local antes de enviarlo a través de ssh, pero parece que ssh se bloqueará esperando a que se abra la tubería en la entrada estándar. grep no abre la tubería para escribir hasta que recibe algo, por lo que básicamente esto se bloquea. catsin embargo, no parece tener el mismo comportamiento y abre la tubería para escritura inmediatamente, permitiendo que ssh abra la conexión.

este es el mismo problema en el lado remoto, y por qué readlínea por línea en lugar de simplemente grep desde stdin; de lo contrario, `/tmp/allocatedports' no se escribe hasta que se cierra el túnel ssh, lo que anula todo el propósito.

Se prefiere canalizar el stderr de ssh en un comando como ~/getsshport.sh, ya que sin especificar un comando, el texto del banner o cualquier otra cosa que esté en la canalización se ejecuta en el shell remoto.

Respuesta3

Tome dos (consulte el historial para ver una versión que noscpdesde el lado del servidor y es un poco más simple), esto debería ser suficiente. La esencia es esta:

  1. pasar una variable de entorno del cliente al servidor, diciéndole al servidor cómo puede detectar cuándo la información del puerto está disponible y luego obtenerla y usarla.
  2. Una vez que la información del puerto esté disponible, cópiela del cliente al servidor, permitiendo que el servidor la obtenga (con la ayuda de la parte 1 anterior) y úsela.

Primero, configure en el lado remoto, debe habilitar el envío de una variable env ensshdconfiguración:

sudo yourfavouriteeditor /etc/ssh/sshd_config

Busque la línea con AcceptEnvy agréguela MY_PORT_FILE(o agregue la línea en Hostla sección derecha si aún no hay una). Para mí la línea se convirtió en esta:

AcceptEnv LANG LC_* MY_PORT_FILE

Recuerda también reiniciarsshdpara que esto surta efecto.

Además, para que los siguientes scripts funcionen, ¡hágalo mkdir ~/portfilesen el lado remoto!


Luego, en el lado local, un fragmento de guión que

  1. crear un nombre de archivo temporal para la redirección stderr
  2. dejar un trabajo en segundo plano para esperar a que el archivo tenga contenido
  3. pasar el nombre del archivo al servidor como variable env, mientras se redirigesshstderr al archivo
  4. El trabajo en segundo plano procede a copiar el archivo temporal stderr al lado del servidor utilizando un método separado.scp
  5. El trabajo en segundo plano también copia un archivo de bandera al servidor para indicar que el archivo stderr está listo

El fragmento de guión:

REMOTE=$USER@datserver

PORTFILE=`mktemp /tmp/sshdataserverports-$(hostname)-XXXXX`
test -e $PORTFILE && rm -v $PORTFILE

# EMPTYFLAG servers both as empty flag file for remote side,
# and safeguard for background job termination on this side
EMPTYFLAG=$PORTFILE-empty
cp /dev/null $EMPTYFLAG

# this variable has the file name sent over ssh connection
export MY_PORT_FILE=$(basename $PORTFILE)

# background job loop to wait for the temp file to have data
( while [ -f $EMPTYFLAG -a \! -s $PORTFILE ] ; do
     sleep 1 # check once per sec
  done
  sleep 1 # make sure temp file gets the port data

  # first copy temp file, ...
  scp  $PORTFILE $REMOTE:portfiles/$MY_PORT_FILE

  # ...then copy flag file telling temp file contents are up to date
  scp  $EMPTYFLAG $REMOTE:portfiles/$MY_PORT_FILE.flag
) &

# actual ssh terminal connection    
ssh -X -o "SendEnv MY_PORT_FILE" -R0:localhost:631 -R0:localhost:4713 $REMOTE 2> $PORTFILE

# remove files after connection is over
rm -v $PORTFILE $EMPTYFLAG

Luego un fragmento del lado remoto, adecuado para.bashrc:

# only do this if subdir has been created and env variable set
if [ -d ~/portfiles -a "$MY_PORT_FILE" ] ; then

       PORTFILE=~/portfiles/$(basename "$MY_PORT_FILE")
       FLAGFILE=$PORTFILE.flag
       # wait for FLAGFILE to get copied,
       # after which PORTFILE should be complete
       while [ \! -f "$FLAGFILE" ] ; do 
           echo "Waiting for $FLAGFILE..."
           sleep 1
       done

       # use quite exact regexps and head to make this robust
       export CUPS_SERVER=localhost:$(grep '^Allocated port [0-9]\+ .* localhost:631[[:space:]]*$' "$PORTFILE" | head -1 | cut -d" " -f3)
       export PULSE_SERVER=localhost:$(grep '^Allocated port [0-9]\+ .* localhost:4713[[:space:]]*$' "$PORTFILE" | head -1 | cut -d" " -f3)
       echo "Set CUPS_SERVER and PULSE_SERVER"

       # copied files served their purpose, and can be removed right away
       rm -v -- "$PORTFILE" "$FLAGFILE"
fi

Nota: El código anterior, por supuesto, no se ha probado muy exhaustivamente y podría contener todo tipo de errores, errores de copiar y pegar, etc. Es mejor que cualquiera que lo use también lo comprenda.¡Úselo bajo su propio riesgo!Lo probé usando solo una conexión de host local y funcionó para mí en mi entorno de prueba. YMMV.

Respuesta4

Esto es complicado, un manejo adicional del lado del servidor similar a SSH_CONNECTIONo DISPLAYsería excelente, pero no es fácil de agregar: parte del problema es que solo el sshcliente conoce el destino local, el paquete de solicitud (al servidor) contiene sólo la dirección remota y el puerto.

Las otras respuestas aquí tienen varias soluciones poco atractivas para capturar este lado del cliente y enviarlo al servidor. Aquí hay un enfoque alternativo que, para ser honesto, no es mucho más bonito, pero al menos esta fea fiesta se mantiene del lado del cliente ;-)

  • lado del cliente, agregar/modificar SendEnvpara que podamos enviar algunas variables de entorno de forma nativa a través de ssh (probablemente no por defecto)
  • lado del servidor, agregar/modificar AcceptEnvpara aceptar lo mismo (probablemente no habilitado de forma predeterminada)
  • monitorear la sshsalida stderr del cliente con una biblioteca cargada dinámicamente y actualizar el entorno del cliente sshdurante el establecimiento de la conexión
  • seleccione las variables de entorno del lado del servidor en el script de perfil/inicio de sesión

Esto funciona (afortunadamente, al menos por ahora) porque los reenvíos remotos se configuran y registran antes de que se intercambie el entorno (confirme con ssh -vv ...). La biblioteca cargada dinámicamente tiene que capturar la write()función libc ( ssh_confirm_remote_forward()logit()do_log()write()). Redirigir o ajustar funciones en un binario ELF (sin recompilar) es mucho más complejo que hacer lo mismo con una función en una biblioteca dinámica.

En el cliente .ssh/config(o línea de comando -o SendEnv ...)

Host somehost
  user whatever
  SendEnv SSH_RFWD_*

En el servidor sshd_config(se requiere cambio raíz/administrativo)

AcceptEnv LC_* SSH_RFWD_*

Este enfoque funciona para clientes Linux y no requiere nada especial en el servidor; debería funcionar para otros *nix con algunos ajustes menores. Funciona desde al menos OpenSSH 5.8p1 hasta 7.5p1.

Compilar con gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c Invocar con:

LD_PRELOAD=./rfwd.so ssh -R0:127.0.0.1:4713 -R0:localhost:631 somehost

El código:

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <stdlib.h>

// gcc -Wall -shared  -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c

#define DEBUG 0
#define dfprintf(fmt, ...) \
    do { if (DEBUG) fprintf(stderr, "[%14s#%04d:%8s()] " fmt, \
          __FILE__, __LINE__, __func__,##__VA_ARGS__); } while (0)

typedef ssize_t write_fp(int fd, const void *buf, size_t count);
static write_fp *real_write;

void myinit(void) __attribute__((constructor));
void myinit(void)
{
    void *dl;
    dfprintf("It's alive!\n");
    if ((dl=dlopen(NULL,RTLD_NOW))) {
        real_write=dlsym(RTLD_NEXT,"write");
        if (!real_write) dfprintf("error: %s\n",dlerror());
        dfprintf("found %p write()\n", (void *)real_write);
    } else {
        dfprintf(stderr,"dlopen() failed\n");
    }
}

ssize_t write(int fd, const void *buf, size_t count)
{
     static int nenv=0;

     // debug1: Remote connections from 192.168.0.1:0 forwarded to local address 127.0.0.1:1000
     //  Allocated port 44284 for remote forward to 127.0.0.1:1000
     // debug1: All remote forwarding requests processed
     if ( (fd==2) && (!strncmp(buf,"Allocated port ",15)) ) {
         char envbuf1[256],envbuf2[256];
         unsigned int rport;
         char lspec[256];
         int rc;

         rc=sscanf(buf,"Allocated port %u for remote forward to %256s",
          &rport,lspec);

         if ( (rc==2) && (nenv<32) ) {
             snprintf(envbuf1,sizeof(envbuf1),"SSH_RFWD_%i",nenv++);
             snprintf(envbuf2,sizeof(envbuf2),"%u %s",rport,lspec);
             setenv(envbuf1,envbuf2,1);
             dfprintf("setenv(%s,%s,1)\n",envbuf1,envbuf2);
         }
     }
     return real_write(fd,buf,count);
}

(Existen algunas trampas para osos de glibc relacionadas con el control de versiones de símbolos con este enfoque, pero write()no tienen este problema).

Si te sientes valiente, puedes tomar el setenv()código relacionado y parchearlo en ssh.c ssh_confirm_remote_forward()la función de devolución de llamada.

Esto establece variables de entorno denominadas SSH_RFWD_nnn, inspecciónelas en su perfil, por ejemplo, enbash

for fwd in ${!SSH_RFWD_*}; do
    IFS=" :" read lport rip rport <<< ${!fwd}
    [[ $rport -eq "631" ]] && export CUPS_SERVER=localhost:$lport
    # ...
done

Advertencias:

  • no hay muchos errores al verificar el código
  • cambiando el medio ambientepuedecausa problemas relacionados con subprocesos, PAM usa subprocesos, no espero problemas pero no lo he probado
  • sshactualmente no registra claramente el reenvío completo del formulario * local:puerto:remoto:puerto* (si es necesario, se requerirá un análisis adicional de debug1los mensajes ssh -v), pero no necesita esto para su caso de uso

Por extraño que parezca, OpenSSH no parece tener medios para recuperar información sobre reenvíos de puertos.

Puede hacer esto (en parte) de forma interactiva con escape ~#; curiosamente, la implementación omite los canales que están escuchando, solo enumera los abiertos (es decir, TCP ESTABLECIDO) y no imprime los campos útiles en ningún caso. Verchannels.c channel_open_message()

Puede parchear esa función para imprimir los detalles de SSH_CHANNEL_PORT_LISTENERlas ranuras, pero eso solo le proporciona los reenvíos locales (canalesno son lo mismo que los realeshacia adelante). O puede parchearlo para volcar las dos tablas de reenvío de la optionsestructura global:

#include "readconf.h"
Options options;  /* extern */
[...]
snprintf(buf, sizeof buf, "Local forwards:\r\n");
buffer_append(&buffer, buf, strlen(buf));
for (i = 0; i < options.num_local_forwards; i++) {
     snprintf(buf, sizeof buf, "  #%d listen %s:%d connect %s:%d\r\n",i,
       options.local_forwards[i].listen_host,
       options.local_forwards[i].listen_port,
       options.local_forwards[i].connect_host,
       options.local_forwards[i].connect_port);
     buffer_append(&buffer, buf, strlen(buf));
}
snprintf(buf, sizeof buf, "Remote forwards:\r\n");
buffer_append(&buffer, buf, strlen(buf));
for (i = 0; i < options.num_remote_forwards; i++) {
     snprintf(buf, sizeof buf, "  #%d listen %s:%d connect %s:%d\r\n",i,
       options.remote_forwards[i].listen_host,
       options.remote_forwards[i].listen_port,
       options.remote_forwards[i].connect_host,
       options.remote_forwards[i].connect_port);
     buffer_append(&buffer, buf, strlen(buf));
}

Esto funciona bien, aunque no es una solución "programática", con la salvedad de que el código del cliente no actualiza (aún, está marcado como XXX en el código fuente) la lista cuando agrega/elimina reenvíos sobre la marcha ( ~C).


Si los servidores son Linux, tiene una opción más, esta es la que uso generalmente, aunque para reenvío local en lugar de remoto. loes 127.0.0.1/8, en Linux puedesvincularse de forma transparente a cualquier dirección en 127/8, por lo que puede utilizar puertos fijos si utiliza direcciones 127.xyz únicas, por ejemplo:

mr@local:~$ ssh -R127.53.50.55:44284:127.0.0.1:44284 remote
[...]
mr@remote:~$ ss -atnp src 127.53.50.55
State      Recv-Q Send-Q        Local Address:Port          Peer Address:Port 
LISTEN     0      128            127.53.50.55:44284                    *:*    

Esto está sujeto a la vinculación de puertos privilegiados <1024, OpenSSH no admite capacidades de Linux y tiene una verificación de UID codificada en la mayoría de las plataformas.

Los octetos sabiamente elegidos (mnemónicos ordinales ASCII en mi caso) ayudan a desenredar el lío al final del día.

información relacionada