Określ dynamicznie przydzielany port dla OpenSSH RemoteForward

13

Pytanie (TL; DR)

Jak dynamicznie przypisywać porty do zdalnego przekazywania (inaczej -Ropcja), w jaki sposób skrypt na zdalnej maszynie (na przykład pochodzącej z .bashrc) może określić, które porty zostały wybrane przez OpenSSH?


tło

Używam OpenSSH (na obu końcach) do łączenia się z naszym centralnym serwerem, który udostępniam wielu innym użytkownikom. Do mojej zdalnej sesji (na razie) chciałbym przesłać X, puchary i pulseaudio.

Najbardziej trywialnym jest przekazywanie X za pomocą -Xopcji. Przydzielony adres X jest przechowywany w zmiennej środowiskowej DISPLAYi na tej podstawie mogę określić odpowiedni port TCP, w większości przypadków i tak. Ale prawie nigdy nie muszę, bo Xlib honoruje DISPLAY.

Potrzebuję podobnego mechanizmu do filiżanek i pulseaudio. Istnieją podstawy dla obu usług, w postaci odpowiednio zmiennych środowiskowych CUPS_SERVERi PULSE_SERVER. Oto przykłady użycia:

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

Problem jest ustawiony CUPS_SERVERi PULSE_SERVERpoprawnie.

Często korzystamy z przekierowania portów, dlatego potrzebuję dynamicznych przydziałów portów. Statyczne przydziały portów nie są opcją.

OpenSSH ma mechanizm dynamicznej alokacji portów na zdalnym serwerze, określając 0jako bind-port do zdalnego przekazywania ( -Ropcja). Korzystając z następującego polecenia, OpenSSH dynamicznie przydziela porty dla pucharów i przekazywania impulsów.

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

Gdy użyję tego polecenia, sshwydrukuję następujące informacje 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

Potrzebuję informacji! Ostatecznie chcę wygenerować:

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

Jednak komunikaty „Przydzielony port ...” są tworzone na moim komputerze lokalnym i wysyłane do STDERR, do którego nie mam dostępu na komputerze zdalnym. Co dziwne, OpenSSH wydaje się nie mieć środków do pobierania informacji o przekierowaniu portów.

Jak pobrać te informacje, aby umieścić je w skrypcie powłoki, aby odpowiednio ustawić CUPS_SERVERi PULSE_SERVERna zdalnym hoście?


Dead Ends

Jedyną łatwą rzeczą, jaką mogłem znaleźć, było zwiększenie szczegółowości, sshddopóki informacje te nie zostaną odczytane z dzienników. Nie jest to wykonalne, ponieważ informacje te ujawniają znacznie więcej informacji niż jest to rozsądne, aby udostępnić je użytkownikom innym niż root.

Zastanawiałem się nad załataniem OpenSSH do obsługi dodatkowej sekwencji ucieczki, która wypisuje ładną reprezentację wewnętrznej struktury permitted_opens, ale nawet jeśli tego właśnie chcę, nadal nie mogę uzyskać skryptu uzyskującego dostęp do sekwencji ucieczki klienta po stronie serwera.


Musi być lepszy sposób

Poniższe podejście wydaje się bardzo niestabilne i jest ograniczone do jednej takiej sesji SSH na użytkownika. Potrzebuję jednak co najmniej dwóch równoczesnych sesji i jeszcze więcej użytkowników. Ale próbowałem ...

Gdy gwiazdy są odpowiednio wyrównane, po poświęceniu jednego lub dwóch kurczaków, mogę nadużywać faktu, że sshdnie został uruchomiony jako mój użytkownik, ale po upuszczeniu upuszcza uprawnienia, aby to zrobić:

  • uzyskać listę numerów portów dla wszystkich gniazd nasłuchujących należących do mojego użytkownika

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

  • uzyskać listę numerów portów dla wszystkich gniazd nasłuchujących należących do procesów uruchomionych przez mojego użytkownika

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

  • Wszystkie porty, które są w pierwszym secie, ale nie w drugim zestawie mają wysoką Likelyhood być moim porty spedycji, a nawet odjęcie wydajności zestawów 41273, 55710a 6010; kubki, puls i X, odpowiednio.

  • 6010jest identyfikowany jako port X za pomocą DISPLAY.

  • 41273jest portem kubków, ponieważ lpstat -h localhost:41273 -azwraca 0.
  • 55710jest portem impulsowym, ponieważ pactl -s localhost:55710 statzwraca 0. (Drukuje nawet nazwę hosta mojego klienta!)

(Aby wykonać odejmowanie zestawu I sort -ui zapisać dane wyjściowe z powyższych wierszy poleceń i użyć commdo odejmowania.)

Pulseaudio pozwala mi zidentyfikować klienta i, dla wszystkich celów i celów, może to służyć jako kotwica do oddzielnych sesji SSH, które wymagają oddzielenia. Jednak nie znaleźli sposób, aby związać 41273, 55710i 6010do tego samego sshdprocesu. netstatnie ujawni tych informacji użytkownikom innym niż root. Dostaję tylko -w PID/Program namekolumnie, w której chciałbym przeczytać 2339/54(w tym konkretnym przypadku). Tak blisko ...

Bananguin
źródło
fwiw, dokładniej jest powiedzieć, że netstatnie pokaże PID dla procesów, których nie jesteś właścicielem lub które są przestrzenią jądra. Na przykład
Bratchley
Najsolidniejszym sposobem byłoby załatanie sshd ... Szybka i brudna łatka to tylko kilka wierszy w miejscu, w którym serwer pobiera port lokalny z systemu operacyjnego, zapisując numer portu do pliku, nazwę wygenerowaną od użytkownika, hosta zdalnego i Port. Zakładając, że serwer zna port po stronie klienta, co nie jest pewne, może nawet nie jest prawdopodobne (w przeciwnym razie funkcja już istniałaby).
hyde
@hyde: dokładnie. Serwer zdalny nie wie o przekierowanych portach. Tworzy tylko kilka gniazd nasłuchowych, a dane są przesyłane przez połączenie ssh. Nie wie o lokalnych portach docelowych.
Bananguin

Odpowiedzi:

1

Weź dwa (zobacz historię wersji, która wykonuje scp ze strony serwera i jest nieco prostsza), to powinno to zrobić. Istota tego jest taka:

  1. przekazać zmienną środowiskową między klientem a serwerem, informując serwer, w jaki sposób może wykryć dostępność informacji o porcie, a następnie pobrać ją i wykorzystać.
  2. gdy informacje o porcie będą dostępne, skopiuj je z klienta na serwer, umożliwiając serwerowi uzyskanie go (z pomocą części 1 powyżej) i użyj go

Po pierwsze, zainstaluj po stronie zdalnej, musisz włączyć wysyłanie zmiennej env w konfiguracji sshd :

sudo yourfavouriteeditor /etc/ssh/sshd_config

Znajdź linię AcceptEnvi dodaj MY_PORT_FILEdo niej (lub dodaj linię w prawej Hostczęści, jeśli jeszcze jej nie ma). Dla mnie linia stała się następująca:

AcceptEnv LANG LC_* MY_PORT_FILE

Pamiętaj również, aby ponownie uruchomić sshd, aby to zadziałało .

Dodatkowo, aby poniższe skrypty działały, zrób mkdir ~/portfilespo zdalnej stronie!


Następnie po stronie lokalnej: fragment skryptu, który będzie

  1. utwórz nazwę pliku tymczasowego dla przekierowania stderr
  2. pozostaw zadanie w tle, aby poczekać na zawartość pliku
  3. przekazać nazwę pliku do serwera jako zmienną env, przekierowując ssh stderr do pliku
  4. zadanie w tle kontynuuje kopiowanie pliku tymczasowego stderr po stronie serwera przy użyciu osobnego scp
  5. zadanie w tle kopiuje również plik flagi na serwer, aby wskazać, że plik stderr jest gotowy

Fragment skryptu:

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

Następnie fragment dla strony zdalnej, odpowiedni dla .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

Uwaga : powyższy kod nie jest oczywiście dokładnie testowany i może zawierać wszelkiego rodzaju błędy, błędy kopiowania i wklejania itp. Każdy, kto go używa, również go rozumie, używa na własne ryzyko! Przetestowałem to za pomocą połączenia localhost i działało dla mnie w mojej testowej env. YMMV.

hyde
źródło
Co oczywiście wymaga, że ​​mogę scpod strony zdalnej do strony lokalnej, czego nie mogę. Miałem podobne podejście, ale sshpo nawiązaniu połączenia owinąłem go w tło, a następnie wyślę ten plik z lokalnego do zdalnego za pośrednictwem, scpa następnie pociągnę sshklienta na pierwszy plan i uruchom skrypt po zdalnej stronie. Nie wymyśliłem, jak ładnie napisać skrypt do tworzenia tła i planowania lokalnych i zdalnych procesów. Zawijanie i integrowanie lokalnego sshklienta z takimi zdalnymi skryptami nie wydaje się dobrym podejściem.
Bananguin
Ach Myślę, że powinien tle po stronie klienta SCP tylko: (while [ ... ] ; do sleep 1 ; done ; scp ... )&. Następnie poczekaj na pierwszym planie na serwerze .bashrc(zakładając, że klient wyśle ​​odpowiednią zmienną env) na pojawienie się pliku. Zaktualizuję odpowiedź później po kilku testach (prawdopodobnie nie będzie czasu do jutra).
hyde
@Banguanguin Nowa wersja wykonana. Wydaje się, że działa dla mnie, więc powinien być dostosowany do twojego przypadku użycia. O „miłym podejściu”, tak, ale nie sądzę, żeby było naprawdę możliwe dobre podejście. Informacje należy jakoś przekazać i zawsze będzie to hack, chyba że załatałeś zarówno klienta ssh, jak i serwer, aby zrobić to czysto za pomocą jednego połączenia.
hyde
I coraz częściej myślę o łataniu openssh. Nie wydaje się to wielka sprawa. Informacje są już dostępne. Muszę tylko wysłać to na serwer. Ilekroć serwer otrzymuje takie informacje, zapisuje je~/.ssh-${PID}-forwards
Bananguin
1

Fragment po stronie lokalnej, odpowiedni dla .bashrc:

#!/bin/bash

user=$1
host=$2

sshr() {
# 1. connect, get dynamic port, disconnect  
port=`echo "exit" | ssh -R '*:0:127.0.0.1:52698' -t $1 2>&1 | grep 'Allocated port' | awk '/port/ {print $3;}'`
# 2. reconnect with this port and set remote variable
cmds="ssh -R $port:127.0.0.1:52698 -t $1 bash -c \"export RMATE_PORT=$port; bash\""
($cmds)
}

sshr $user@$host
ToxeH
źródło
0

Osiągnąłem to samo, tworząc potok na lokalnym kliencie, a następnie przekierowując stderr do potoku, który jest również przekierowywany do wejścia ssh. Nie wymaga wielu połączeń ssh, aby założyć wolny znany port, który mógłby zawieść. W ten sposób baner logowania i tekst „Przydzielony port ### ...” są przekierowywane do zdalnego hosta.

Mam prosty skrypt na hoście, getsshport.shktóry jest uruchamiany na hoście zdalnym, który odczytuje przekierowane dane wejściowe i analizuje port. Dopóki ten skrypt nie zakończy się, zdalne przekazywanie ssh pozostaje otwarte.

strona lokalna

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 jest małą sztuczką, aby zamienić stderr i stdout, tak że stderr zostaje przesłany do cat, a wszystkie normalne dane wyjściowe z ssh są wyświetlane na stderr.

strona zdalna ~ / 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

Próbowałem grepnajpierw wyświetlić komunikat „przydzielony port” po stronie lokalnej przed wysłaniem go przez ssh, ale wygląda na to, że ssh zablokuje oczekiwanie na otwarcie potoku na standardowym wejściu. grep nie otwiera potoku do pisania, dopóki czegoś nie otrzyma, więc w zasadzie jest to impas. catjednak wydaje się, że nie zachowuje się tak samo i otwiera potok do natychmiastowego zapisu, pozwalając ssh na otwarcie połączenia.

jest to ten sam problem po drugiej stronie i dlaczego readwiersz po wierszu zamiast po prostu grep ze standardowego wejścia - w przeciwnym razie `/ tmp / przydzieloneports 'nie zostaną zapisane, dopóki tunel ssh nie zostanie zamknięty, co pokona cały cel

~/getsshport.shPreferowane jest umieszczenie stderr ssh w poleceniu podobnym , ponieważ bez podawania polecenia, tekstu banera lub czegokolwiek innego, co znajduje się w potoku, jest ono wykonywane na zdalnej powłoce.

JesseMcL
źródło
miły. dodałem renice +10 $$; exec catprzed, doneaby zaoszczędzić zasoby.
Spongman,
0

Jest to trudna, dodatkowa obsługa po stronie serwera zgodnie z wytycznymi SSH_CONNECTIONlub DISPLAYbyłaby świetna, ale nie jest łatwa do dodania: część problemu polega na tym, że tylko sshklient zna lokalne miejsce docelowe, pakiet żądań (na serwer) zawiera tylko zdalny adres i port.

Inne odpowiedzi tutaj zawierają różne bezpretensjonalne rozwiązania do przechwytywania tej strony klienta i wysyłania jej na serwer. Oto alternatywne podejście, które wcale nie jest ładniejsze, ale przynajmniej ta brzydka impreza jest utrzymywana po stronie klienta ;-)

  • po stronie klienta, dodaj / popraw SendEnv, abyśmy mogli przesyłać niektóre zmienne środowiskowe natywnie przez ssh (prawdopodobnie nie domyślnie)
  • po stronie serwera, dodaj / popraw, AcceptEnvaby zaakceptować to samo (prawdopodobnie domyślnie wyłączone)
  • monitoruj dane sshwyjściowe stderr klienta za pomocą dynamicznie ładowanej biblioteki i aktualizuj środowisko klienta ssh podczas konfigurowania połączenia
  • wybierz zmienne środowiskowe po stronie serwera w skrypcie profilu / logowania

Działa to (na szczęście i tak na razie), ponieważ zdalne przekazywanie jest konfigurowane i rejestrowane przed wymianą środowiska (potwierdź za pomocą ssh -vv ...). Dynamicznie ładowana biblioteka musi przechwycić funkcję write()libc ( ssh_confirm_remote_forward()logit()do_log()write()). Przekierowywanie lub zawijanie funkcji w pliku binarnym ELF (bez ponownej kompilacji) jest o rząd wielkości bardziej złożone niż robienie tego samego dla funkcji w bibliotece dynamicznej.

W kliencie .ssh/config(lub wierszu poleceń -o SendEnv ...)

Host somehost
  user whatever
  SendEnv SSH_RFWD_*

Na serwerze sshd_config(wymagana zmiana administratora / administratora)

AcceptEnv LC_* SSH_RFWD_*

To podejście działa dla klientów Linux i nie wymaga niczego specjalnego na serwerze, powinno działać dla innych * nix z kilkoma drobnymi poprawkami. Działa od co najmniej OpenSSH 5.8p1 do 7.5p1.

Kompiluj za pomocą gcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c Invoke za pomocą:

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

Kod:

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

(Istnieje kilka pułapek na niedźwiedzie glibc związane z wersjonowaniem symboli przy takim podejściu, ale write()nie ma tego problemu).

Jeśli czujesz się odważny, możesz wziąć setenv()odpowiedni kod i wstawić go do ssh.c ssh_confirm_remote_forward()funkcji wywołania zwrotnego.

Ustawia zmienne środowiskowe o nazwie SSH_RFWD_nnn, sprawdzaj je w swoim profilu, np. Wbash

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

Ostrzeżenia:

  • w kodzie nie ma wiele błędów
  • zmiana środowiska może powodować problemy związane z wątkami, PAM używa wątków, nie oczekuję problemów, ale tego nie testowałem
  • sshobecnie nie rejestruje w sposób wyraźny pełnego przekazywania formularza * local: port: remote: port * (w razie potrzeby wymagane byłoby dalsze parsowanie debug1wiadomości ssh -v), ale nie jest to potrzebne w przypadku użycia

Co dziwne, OpenSSH wydaje się nie mieć środków do pobierania informacji o przekierowaniu portów.

Możesz (częściowo) to zrobić interaktywnie za pomocą ucieczki ~#, co dziwne, implementacja przeskakuje nad kanałami, które nasłuchują, wyświetla tylko te otwarte (tj. TCP USTANOWIONE) i nie drukuje użytecznych pól w żadnym wypadku. Widziećchannels.c channel_open_message()

Można załatać tę funkcję do drukowania szczegółów dla SSH_CHANNEL_PORT_LISTENERszczelin, ale to tylko dostaje lokalnych forwardings ( kanały nie są tym samym, co faktycznym forward ). Lub możesz go załatać, aby zrzucić dwie tabele przesyłania z globalnej optionsstruktury:

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

Działa to dobrze, chociaż nie jest to rozwiązanie „programowe”, z zastrzeżeniem, że kod klienta nie (jeszcze oznaczony jako XXX w źródle) aktualizuje listę, gdy dodajesz / usuwasz przekazy w locie ( ~C)


Jeśli serwer (y) to Linux, masz jeszcze jedną opcję, z której korzystam ogólnie, chociaż do lokalnego przekazywania zamiast zdalnej. loto 127.0.0.1/8, w systemie Linux możesz transparentnie powiązać z dowolnym adresem w 127/8 , więc możesz użyć stałych portów, jeśli używasz unikalnych adresów 127.xyz, np .:

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

Jest to uzależnione od wiązania uprzywilejowanych portów <1024, OpenSSH nie obsługuje możliwości Linuksa i ma zakodowane sprawdzenie UID na większości platform.

Mądrze wybrane oktety (w moim przypadku porządkowe mnemoniki ASCII) pomagają rozwikłać bałagan na koniec dnia.

pan. spuratic
źródło