Определить динамически выделенный порт для OpenSSH RemoteForward

Определить динамически выделенный порт для OpenSSH RemoteForward

Вопрос (вкратце)

При динамическом назначении портов для удаленной переадресации ( -Rопция) как скрипт на удаленной машине (например, полученный из .bashrc) может определить, какие порты были выбраны OpenSSH?


Фон

Я использую OpenSSH (на обоих концах) для подключения к нашему центральному серверу, который я разделяю с несколькими другими пользователями. Для моего удаленного сеанса (на данный момент) я хотел бы перенаправить X, cups и pulseaudio.

Самый простой — пересылка X с использованием -Xопции. Выделенный адрес X хранится в переменной окружения DISPLAY, и из него я могу определить соответствующий порт TCP, в большинстве случаев по крайней мере. Но мне это почти никогда не нужно, потому что Xlib уважает DISPLAY.

Мне нужен аналогичный механизм для cups и pulseaudio. Основы для обоих сервисов существуют в виде переменных окружения CUPS_SERVERи PULSE_SERVER, соответственно. Вот примеры использования:

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

Проблема в настройке CUPS_SERVERи PULSE_SERVERправильности.

Мы часто используем переадресацию портов, поэтому мне нужно динамическое распределение портов. Статическое распределение портов не вариант.

OpenSSH имеет механизм динамического выделения портов на удаленном сервере, указав 0в качестве bind-port для удаленной пересылки (опция -R). Используя следующую команду, OpenSSH будет динамически выделять порты для пересылки cups и pulse.

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

При использовании этой команды sshбудет выведено следующее 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

Вот информация, которая мне нужна! В конечном итоге я хочу сгенерировать:

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

Однако сообщения "Allocated port ..." создаются на моей локальной машине и отправляются на STDERR, к которому я не могу получить доступ на удаленной машине. Как ни странно, OpenSSH, похоже, не имеет средств для получения информации о переадресации портов.

Как мне получить эту информацию, чтобы поместить ее в скрипт оболочки для адекватной настройки CUPS_SERVERна PULSE_SERVERудаленном хосте?


Тупики

Единственное, что я смог легко найти, это увеличить детализацию информации, sshdпока ее можно будет прочитать из журналов. Это нежизнеспособно, поскольку эта информация раскрывает гораздо больше информации, чем разумно сделать доступной для пользователей без прав root.

Я думал о том, чтобы исправить OpenSSH для поддержки дополнительной escape-последовательности, которая выводит хорошее представление внутренней структуры permitted_opens, но даже если это то, что мне нужно, я все равно не могу написать скрипт для доступа к escape-последовательностям клиента со стороны сервера.


Должен быть лучший способ.

Следующий подход кажется очень нестабильным и ограничен одним таким сеансом SSH на пользователя. Однако мне нужно как минимум два таких сеанса одновременно, а другим пользователям — еще больше. Но я попробовал...

Когда звезды сложатся должным образом, пожертвовав одной-двумя курами, я смогу воспользоваться тем фактом, что sshdне запускается от имени моего пользователя, но теряет привилегии после успешного входа в систему, чтобы сделать следующее:

  • получить список номеров портов для всех прослушиваемых сокетов, принадлежащих моему пользователю

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

  • получить список номеров портов для всех прослушиваемых сокетов, которые принадлежат процессам, запущенным моим пользователем

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

  • Все порты, которые есть в первом наборе, но не во втором, с высокой вероятностью являются моими портами пересылки, и, действительно, вычитание наборов дает 41273, 55710и 6010; чашки, импульс и X соответственно.

  • 6010идентифицируется как порт X с использованием DISPLAY.

  • 41273порт cups, потому что lpstat -h localhost:41273 -aвозвращает 0.
  • 55710является портом импульса, потому что pactl -s localhost:55710 statвозвращает 0. (Он даже выводит имя хоста моего клиента!)

(Чтобы выполнить вычитание множеств I sort -u, сохраните вывод из приведенных выше командных строк и используйте commдля выполнения вычитания.)

Pulseaudio позволяет мне идентифицировать клиента, и, по всем намерениям и целям, это может служить якорем для разделения сеансов SSH, которые необходимо разделить. Однако я не нашел способа связать 41273, 55710и 6010с тем же sshdпроцессом. netstatне буду раскрывать эту информацию не-root пользователям. Я получаю только -в PID/Program nameстолбце, где я хотел бы прочитать 2339/54(в этом конкретном случае). Так близко ...

решение1

К сожалению, я не нашел ваш вопрос раньше, но только что получил действительно хороший ответ от kamil-maciorowski:

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

Подводя итог, сначала установите главное соединение, оставьте его в фоновом режиме, а затем выполните вторую команду с -O *ctl_cmd*параметром set to forwardдля запроса/настройки переадресации портов:

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

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

Это даст вам возможность работать $portна локальном компьютере и устанавливать соединение в фоновом режиме.

Затем вы можете использовать его $portлокально или использовать его sshснова для запуска команды на удаленном сервере, где вы можете использовать тот же управляющий сокет.

Что касается флагов, то это:

  • = Запрашивает ssh переход в фоновый режим.
  • = Не выполнять удаленную команду.
  • = Переводит клиента в режим «главного» для совместного использования подключения.
  • = Расположение контрольного разъема для совместного использования подключений.
  • = Управление главным процессом мультиплексирования активных соединений.

В моем случае я добавил еще немного, чтобы продолжать проверять соединение:

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

решение2

Я добился того же, создав канал на локальном клиенте, а затем перенаправив stderr в канал, который также перенаправляется на вход ssh. Не требуется множественных соединений ssh, чтобы предположить свободный известный порт, который может дать сбой. Таким образом, баннер входа и текст "Allocated port ### ..." перенаправляются на удаленный хост.

У меня есть простой скрипт на хосте getsshport.sh, который запускается на удаленном хосте, который считывает перенаправленный ввод и анализирует порт. Пока этот скрипт не завершится, удаленный форвард ssh остается открытым.

местная сторона

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небольшой трюк, позволяющий поменять местами stderr и stdout, так что stderr будет передаваться в cat, а весь обычный вывод от ssh будет отображаться в stderr.

удаленная сторона ~/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

Я сначала попробовал передать grepсообщение «выделенный порт» на локальной стороне, прежде чем отправлять его через ssh, но, похоже, ssh блокируется, ожидая открытия канала на stdin. grep не открывает канал для записи, пока не получит что-либо, так что это по сути тупиковая ситуация. catОднако, похоже, что у него иное поведение, и он немедленно открывает канал для записи, позволяя ssh открыть соединение.

та же проблема на удаленной стороне, и почему readпострочно, а не просто grep из stdin - в противном случае `/tmp/allocatedports' не будет записан, пока не будет закрыт туннель ssh, что сводит на нет весь смысл

Предпочтительнее передавать stderr ssh в команду типа like ~/getsshport.sh, так как без указания команды текст баннера или что-либо еще, находящееся в канале, будет выполнено в удаленной оболочке.

решение3

Возьмите два (см. историю для версии, которая делаетscpсо стороны сервера и немного проще), это должно сделать это. Суть в следующем:

  1. передать переменную среды от клиента к серверу, сообщая серверу, как он может определить, доступна ли информация о порте, а затем получить и использовать ее.
  2. как только информация о порте станет доступна, скопируйте ее с клиента на сервер, разрешив серверу получить ее (с помощью части 1 выше), и используйте ее

Во-первых, настройте удаленную сторону, вам нужно включить отправку переменной env вsshdконфигурация:

sudo yourfavouriteeditor /etc/ssh/sshd_config

Найдите строку с AcceptEnvи добавьте MY_PORT_FILEк ней (или добавьте строку под правым Hostразделом, если ее еще нет). Для меня строка стала такой:

AcceptEnv LANG LC_* MY_PORT_FILE

Также не забудьте перезагрузитьsshdчтобы это вступило в силу.

Кроме того, чтобы приведенные ниже скрипты работали, делайте это mkdir ~/portfilesна удаленной стороне!


Затем на локальной стороне, фрагмент скрипта, который будет

  1. создать имя временного файла для перенаправления stderr
  2. оставить фоновое задание, ожидающее, пока файл будет иметь содержимое
  3. передать имя файла на сервер как переменную env при перенаправлениисшstderr в файл
  4. фоновое задание продолжает копировать временный файл stderr на серверную сторону, используя отдельныйscp
  5. фоновое задание также копирует файл флага на сервер, чтобы указать, что файл stderr готов

Фрагмент сценария:

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

Затем фрагмент для удаленной стороны, подходящий для.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

Примечание: Приведенный выше код, конечно, не очень тщательно протестирован и может содержать всевозможные ошибки, ошибки копирования-вставки и т. д. Любой, кто его использует, должен также его понимать,Используйте на свой риск!Я протестировал его, используя только локальное соединение, и у меня это сработало в моей тестовой среде YMMV.

решение4

Это сложный вопрос, дополнительная обработка на стороне сервера по типу SSH_CONNECTIONили DISPLAYбыла бы замечательна, но ее нелегко добавить: часть проблемы в том, что только клиент sshзнает локальное назначение, пакет запроса (к серверу) содержит только удаленный адрес и порт.

Другие ответы здесь содержат различные некрасивые решения для захвата этой клиентской стороны и отправки ее на сервер. Вот альтернативный подход, который, честно говоря, не намного красивее, но, по крайней мере, эта уродливая вечеринка сохраняется на клиентской стороне ;-)

  • на стороне клиента, добавьте/измените SendEnv, чтобы мы могли отправлять некоторые переменные среды напрямую через ssh (вероятно, не по умолчанию)
  • на стороне сервера, добавить/изменить AcceptEnv, чтобы принять то же самое (вероятно, не включено по умолчанию)
  • контролировать sshвывод stderr клиента с помощью динамически загружаемой библиотеки и обновлять среду клиента sshво время настройки соединения
  • выбрать переменные среды на стороне сервера в скрипте профиля/входа

Это работает (к счастью, по крайней мере, пока), потому что удаленные пересылки настраиваются и записываются до обмена средой (подтвердите с помощью ssh -vv ...). Динамически загружаемая библиотека должна захватывать write()функцию libc ( ssh_confirm_remote_forward()logit()do_log()write()). Перенаправление или обертывание функций в двоичном файле ELF (без перекомпиляции) на несколько порядков сложнее, чем выполнение того же самого для функции в динамической библиотеке.

На клиенте .ssh/config(или в командной строке -o SendEnv ...)

Host somehost
  user whatever
  SendEnv SSH_RFWD_*

На сервере sshd_config(требуется изменение прав root/администратора)

AcceptEnv LC_* SSH_RFWD_*

Этот подход работает для клиентов Linux и не требует ничего особенного на сервере, он должен работать для других *nix с некоторыми небольшими изменениями. Работает как минимум с OpenSSH 5.8p1 до 7.5p1.

Скомпилировать с помощью gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c Invoke с:

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

Код:

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

(При таком подходе есть некоторые ловушки glibc, связанные с управлением версиями символов, но write()у него такой проблемы нет.)

Если вы чувствуете себя смелым, вы можете взять setenv()соответствующий код и вставить его в ssh.c ssh_confirm_remote_forward()функцию обратного вызова.

Это устанавливает переменные среды с именами SSH_RFWD_nnn, проверьте их в своем профиле, например, вbash

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

Предостережения:

  • в коде не так много проверок на ошибки
  • изменение окружающей средыможетвызывают проблемы, связанные с потоками, PAM использует потоки, я не ожидаю проблем, но я это не проверял
  • sshв настоящее время явно не регистрирует полную пересылку в форме * local:port:remote:port* (при необходимости потребуется дальнейший анализ debug1сообщений ), но вам это не нужно для вашего варианта использованияssh -v

Как ни странно, OpenSSH, похоже, не имеет средств для получения информации о переадресации портов.

Вы можете (частично) сделать это интерактивно с помощью escape ~#, как ни странно, реализация пропускает прослушиваемые каналы, она перечисляет только открытые (т. е. TCP ESTABLISHED), и в любом случае не выводит полезные поля. Смотритеchannels.c channel_open_message()

Вы можете исправить эту функцию, чтобы она выводила сведения о SSH_CHANNEL_PORT_LISTENERслотах, но это даст вам только локальные переадресации (каналыне то же самое, что и настоящиевперед). Или вы можете исправить его, чтобы выгрузить две таблицы пересылки из глобальной optionsструктуры:

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

Это работает отлично, хотя это не «программное» решение, с той оговоркой, что клиентский код не обновляет список (пока что он помечен как XXX в исходном коде), когда вы добавляете/удаляете пересылки «на лету» ( ~C)


Если сервер(ы) работают на Linux, у вас есть еще один вариант, это тот, который я обычно использую, хотя для локальной пересылки, а не удаленной. loЭто 127.0.0.1/8, на Linux вы можетепрозрачно привязать к любому адресу в 127/8, поэтому вы можете использовать фиксированные порты, если используете уникальные адреса 127.xyz, например:

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

Это зависит от привязки привилегированных портов <1024, OpenSSH не поддерживает возможности Linux и имеет жестко запрограммированную проверку UID на большинстве платформ.

Грамотно выбранные октеты (в моем случае это порядковые мнемоники ASCII) помогают распутать путаницу в конце дня.

Связанный контент