Determine a porta alocada dinamicamente para OpenSSH RemoteForward

Determine a porta alocada dinamicamente para OpenSSH RemoteForward

Pergunta (TL; DR)

Ao atribuir portas dinamicamente para encaminhamento remoto (também conhecido como -Ropção), como um script na máquina remota (por exemplo, originado de .bashrc) pode determinar quais portas foram escolhidas pelo OpenSSH?


Fundo

Eu uso OpenSSH (em ambas as extremidades) para conectar-me ao nosso servidor central, que compartilho com vários outros usuários. Para minha sessão remota (por enquanto), gostaria de encaminhar X, cups e pulseaudio.

O mais trivial é encaminhar X, usando a -Xopção. O endereço X alocado é armazenado na variável ambiental DISPLAYe a partir disso posso determinar a porta TCP correspondente, na maioria dos casos, de qualquer maneira. Mas quase nunca preciso, porque o Xlib honra DISPLAY.

Preciso de um mecanismo semelhante para cups e pulseaudio. Os fundamentos para ambos os serviços existem, na forma das variáveis ​​ambientais CUPS_SERVERe PULSE_SERVER, respectivamente. Aqui estão exemplos 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

O problema está na configuração CUPS_SERVERe PULSE_SERVERcorretamente.

Usamos muito encaminhamento de porta e, portanto, preciso de alocações dinâmicas de porta. As alocações de portas estáticas não são uma opção.

OpenSSH possui um mecanismo para alocação dinâmica de portas no servidor remoto, especificando 0como bind-port para encaminhamento remoto (a -Ropção). Usando o comando a seguir, o OpenSSH alocará portas dinamicamente para cups e encaminhamento de pulso.

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

Quando eu uso esse comando, sshimprimirei o seguinte em 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

Aí está a informação que eu quero! Em última análise, quero gerar:

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

No entanto, as mensagens "Porta alocada ..." são criadas na minha máquina local e enviadas para STDERR, que não consigo acessar na máquina remota. Curiosamente, o OpenSSH não parece ter meios de recuperar informações sobre encaminhamentos de porta.

Como faço para buscar essas informações para colocá-las em um script de shell para configurá-las adequadamente CUPS_SERVERe PULSE_SERVERno host remoto?


Becos-sem-saída

A única coisa fácil que consegui encontrar foi aumentar a verbosidade sshdaté que as informações pudessem ser lidas nos logs. Isso não é viável, pois essas informações divulgam muito mais informações do que seria sensato tornar acessíveis a usuários não-root.

Eu estava pensando em corrigir o OpenSSH para suportar uma sequência de escape adicional que imprime uma boa representação do interno struct permitted_opens, mas mesmo que seja isso que eu quero, ainda não consigo criar scripts para acessar as sequências de escape do cliente do lado do servidor.


Deve haver uma maneira melhor

A abordagem a seguir parece muito instável e está limitada a uma sessão SSH por usuário. No entanto, preciso de pelo menos duas sessões simultâneas e de outros usuários ainda mais. Mas eu tentei...

Quando as estrelas estão alinhadas corretamente, tendo sacrificado uma ou duas galinhas, posso abusar do fato de sshdnão ser iniciado como meu usuário, mas descartar privilégios após login bem-sucedido, para fazer isso:

  • obter uma lista de números de porta para todos os soquetes de escuta que pertencem ao meu usuário

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

  • obter uma lista de números de porta para todos os soquetes de escuta que pertencem aos processos iniciados pelo meu usuário

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

  • Todas as portas que estão no primeiro conjunto, mas não no segundo conjunto, têm uma grande probabilidade de serem minhas portas de encaminhamento e, de fato, subtrair os rendimentos dos conjuntos 41273e ; xícaras, pulso e X, respectivamente.557106010

  • 6010é identificado como a porta X usando DISPLAY.

  • 41273é a porta cups, porque lpstat -h localhost:41273 -aretorna 0.
  • 55710é a porta de pulso, porque pactl -s localhost:55710 statretorna 0. (Até imprime o nome do host do meu cliente!)

(Para fazer o conjunto de subtração I sort -ue armazenar a saída das linhas de comando acima e usar commpara fazer a subtração.)

O Pulseaudio me permite identificar o cliente e, para todos os efeitos, isso pode servir como uma âncora para separar sessões SSH que precisam ser separadas. No entanto, não encontrei uma maneira de vincular 41273e 55710ao 6010mesmo sshdprocesso. netstatnão divulgará essas informações para usuários não-root. Só recebo um -na PID/Program namecoluna onde gostaria de ler 2339/54(neste caso específico). Tão perto ...

Responder1

Infelizmente não encontrei sua pergunta antes, mas acabei de receber uma resposta muito boa de Kamil-maciorowski:

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

Em resumo, estabeleça primeiro uma conexão mestre, mantenha-a em segundo plano e, em seguida, emita um segundo comando -O *ctl_cmd*definido como forwardpara solicitar/configurar o encaminhamento de porta:

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

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

Isso fornecerá a você $portsua máquina local e uma conexão em segundo plano.

Você pode então usar $portlocalmente; ou use sshnovamente para executar um comando no servidor remoto, onde você pode usar o mesmo soquete de controle.

Quanto às bandeiras, são elas:

  • -f= Solicita que o ssh vá para segundo plano.
  • -N= Não execute um comando remoto.
  • -M= Coloca o cliente no modo “master”, para compartilhamento de conexão.
  • -S= Localização de um soquete de controle para compartilhamento de conexão.
  • -O= Controlar um processo mestre de multiplexação de conexão ativa.

No meu caso, adicionei um pouco mais para continuar verificando a conexão:

#!/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

Responder2

Consegui o mesmo criando um pipe no cliente local e, em seguida, redirecionando o stderr para o pipe que também é redirecionado para a entrada do ssh. Não são necessárias múltiplas conexões ssh para presumir uma porta conhecida livre que pode falhar. Dessa forma, o banner de logon e o texto "Porta alocada ### ..." são redirecionados para o host remoto.

Eu tenho um script simples no host getsshport.shque é executado no host remoto, que lê a entrada redirecionada e analisa a porta. Enquanto esse script não terminar, o encaminhamento remoto do ssh permanecerá aberto.

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>&3é um pequeno truque para trocar stderr e stdout, de modo que stderr seja canalizado para cat e toda a saída normal do ssh seja mostrada em 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

Eu tentei grepa mensagem "porta alocada" no lado local antes de enviá-la através do ssh, mas parece que o ssh bloqueará a espera pela abertura do canal no stdin. grep não abre o pipe para escrita até receber algo, então isso basicamente gera um impasse. catno entanto, não parece ter o mesmo comportamento e abre o canal para gravação imediatamente, permitindo que o ssh abra a conexão.

este é o mesmo problema no lado remoto, e por que readlinha por linha em vez de apenas grep from stdin - caso contrário `/tmp/allocatedports' não é escrito até que o túnel ssh seja fechado, o que anula todo o propósito

É preferível canalizar o stderr do ssh para um comando como ~/getsshport.sh, pois sem especificar um comando, o texto do banner ou qualquer outra coisa que esteja no canal é executado no shell remoto.

Responder3

Pegue dois (veja o histórico para uma versão que nãoscpdo lado do servidor e é um pouco mais simples), isso deve bastar. A essência disso é esta:

  1. passar uma variável de ambiente do cliente para o servidor, informando ao servidor como ele pode detectar quando as informações da porta estão disponíveis e então obtê-las e usá-las.
  2. assim que as informações da porta estiverem disponíveis, copie-as do cliente para o servidor, permitindo que o servidor as obtenha (com a ajuda da parte 1 acima) e use-as

Primeiro, configure no lado remoto, você precisa habilitar o envio de uma variável env emsshdconfiguração:

sudo yourfavouriteeditor /etc/ssh/sshd_config

Encontre a linha AcceptEnve adicione MY_PORT_FILE-a (ou adicione a linha na Hostseção direita, se ainda não houver). Para mim a linha ficou assim:

AcceptEnv LANG LC_* MY_PORT_FILE

Lembre-se também de reiniciarsshdpara que isso tenha efeito.

Além disso, para que os scripts abaixo funcionem, faça- mkdir ~/portfileso remotamente!


Então, no lado local, um trecho de script que irá

  1. crie um nome de arquivo temporário para redirecionamento stderr
  2. deixe um trabalho em segundo plano para esperar que o arquivo tenha conteúdo
  3. passe o nome do arquivo para o servidor como variável env, enquanto redirecionasshstderr para o arquivo
  4. trabalho em segundo plano continua copiando o arquivo temporário stderr para o lado do servidor usando separadoscp
  5. o trabalho em segundo plano também copia um arquivo de sinalização para o servidor para indicar que o arquivo stderr está pronto

O trecho do script:

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

Em seguida, um trecho para o lado remoto, adequado 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

Observação: O código acima, obviamente, não foi testado exaustivamente e pode conter todos os tipos de bugs, erros de copiar e colar, etc.Use por sua conta e risco!Eu testei usando apenas a conexão localhost e funcionou para mim, no meu ambiente de teste. YMMV.

Responder4

Este é complicado, o tratamento extra do lado do servidor nos moldes de SSH_CONNECTIONor DISPLAYseria ótimo, mas não é fácil de adicionar: parte do problema é que apenas o sshcliente conhece o destino local, o pacote de solicitação (para o servidor) contém apenas o endereço remoto e a porta.

As outras respostas aqui possuem várias soluções pouco bonitas para capturar esse lado do cliente e enviá-lo ao servidor. Aqui está uma abordagem alternativa que não é muito mais bonita para ser honesto, mas pelo menos essa parte feia é mantida do lado do cliente ;-)

  • lado do cliente, adicione/altere SendEnvpara que possamos enviar algumas variáveis ​​de ambiente nativamente por ssh (provavelmente não padrão)
  • lado do servidor, adicione/altere AcceptEnvpara aceitar o mesmo (provavelmente não habilitado por padrão)
  • monitore a sshsaída stderr do cliente com uma biblioteca carregada dinamicamente e atualize o ambiente do cliente sshdurante a configuração da conexão
  • pegue as variáveis ​​de ambiente do lado do servidor no script de perfil/login

Isso funciona (felizmente, pelo menos por enquanto) porque os encaminhamentos remotos são configurados e gravados antes da troca do ambiente (confirme com ssh -vv ...). A biblioteca carregada dinamicamente deve capturar a write()função libc ( ssh_confirm_remote_forward()logit()do_log()write()). Redirecionar ou agrupar funções em um binário ELF (sem recompilar) é muito mais complexo do que fazer o mesmo para uma função em uma biblioteca dinâmica.

No cliente .ssh/config(ou linha de comando -o SendEnv ...)

Host somehost
  user whatever
  SendEnv SSH_RFWD_*

No servidor sshd_config(alteração raiz/administrativa necessária)

AcceptEnv LC_* SSH_RFWD_*

Esta abordagem funciona para clientes Linux e não requer nada de especial no servidor; deve funcionar para outros *nix com alguns pequenos ajustes. Funciona pelo menos OpenSSH 5.8p1 até 7.5p1.

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

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

O 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);
}

(Existem algumas armadilhas de urso glibc relacionadas ao controle de versão de símbolos com esta abordagem, mas write()não apresentam esse problema.)

Se você estiver com coragem, poderá pegar o setenv()código relacionado e corrigi-lo na ssh.c ssh_confirm_remote_forward()função de retorno de chamada.

Isso define variáveis ​​de ambiente denominadas SSH_RFWD_nnn, inspecione-as em seu perfil, por exemplo, embash

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

Ressalvas:

  • não há muitos erros na verificação do código
  • mudando o ambientepoderiacausar problemas relacionados ao thread, o PAM usa threads, não espero problemas, mas não testei isso
  • sshatualmente não registra claramente o encaminhamento completo no formato * local:port:remote:port* (se necessário, seria necessária uma análise adicional das debug1mensagens ssh -v), mas você não precisa disso para o seu caso de uso

Curiosamente, o OpenSSH não parece ter meios de recuperar informações sobre encaminhamentos de porta.

Você pode (parcialmente) fazer isso interativamente com o escape ~#, estranhamente a implementação pula os canais que estão escutando, lista apenas os abertos (ou seja, TCP ESTABLISHED) e não imprime os campos úteis em nenhum caso. Verchannels.c channel_open_message()

Você pode corrigir essa função para imprimir os detalhes dos SSH_CHANNEL_PORT_LISTENERslots, mas isso só fornece os encaminhamentos locais (canaisnão são a mesma coisa que reaispara a frente). Ou você pode corrigi-lo para despejar as duas tabelas de encaminhamento da optionsestrutura 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));
}

Isso funciona bem, embora não seja uma solução "programática", com a ressalva de que o código do cliente não (ainda está sinalizado como XXX na fonte) atualiza a lista quando você adiciona/remove encaminhamentos on-the-fly ( ~C)


Se o(s) servidor(es) for(em) Linux você tem mais uma opção, esta é a que eu uso geralmente, embora para encaminhamento local em vez de remoto. loé 127.0.0.1/8, no Linux você podevincular-se de forma transparente a qualquer endereço em 127/8, então você pode usar portas fixas se usar endereços 127.xyz exclusivos, por exemplo:

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                    *:*    

Isso está sujeito à ligação de portas privilegiadas <1024, o OpenSSH não suporta recursos do Linux e possui uma verificação de UID codificada na maioria das plataformas.

Octetos sabiamente escolhidos (mnemônicos ordinais ASCII no meu caso) ajudam a desembaraçar a bagunça no final do dia.

informação relacionada