OpenSSH RemoteForward に動的に割り当てられたポートを決定する

OpenSSH RemoteForward に動的に割り当てられたポートを決定する

質問(TL;DR)

リモート転送 (オプションとも呼ばれます) 用にポートを動的に割り当てる場合-R、リモート マシン上のスクリプト (たとえば、 からソース.bashrc) は、OpenSSH によって選択されたポートをどのように判断できますか?


背景

私は、複数の他のユーザーと共有している中央サーバーに接続するために、OpenSSH (両端で) を使用しています。リモート セッションでは (今のところ)、X、cups、pulseaudio を転送したいと思います。

最も簡単なのは、-Xオプションを使用して X を転送することです。割り当てられた X アドレスは環境変数に保存されDISPLAY、ほとんどの場合、そこから対応する TCP ポートを決定できます。ただし、Xlib は を尊重するため、これを行う必要はほとんどありませんDISPLAY

CUPS_SERVERcups と pulseaudio にも同様のメカニズムが必要です。両方のサービスの基本は、それぞれ環境変数とという形で存在します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_SERVERPULSE_SERVER正確さです。

ポート転送を頻繁に使用するため、動的なポート割り当てが必要です。静的なポート割り当ては選択できません。

0OpenSSH には、リモート転送用の 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

しかし、「割り当てられたポート...」メッセージはローカル マシン上で作成され、 に送信されるSTDERRため、リモート マシンからはアクセスできません。奇妙なことに、OpenSSH にはポート転送に関する情報を取得する手段がないようです。

その情報を取得してシェル スクリプトに組み込みCUPS_SERVERPULSE_SERVERリモート ホスト上で適切に設定するにはどうすればよいですか?


行き止まり

私が見つけた唯一の簡単な方法は、ログからその情報を読み取れるまで、の詳細度を上げることでしたsshd。その情報は、非ルート ユーザーがアクセスできるようにするには適切な範囲を超える情報を開示するため、これは実行可能ではありません。

私は、内部構造体の適切な表現を出力する追加のエスケープ シーケンスをサポートするように OpenSSH にパッチを適用することを考えていましたがpermitted_opens、それが目的であっても、サーバー側からクライアントのエスケープ シーケンスにアクセスするスクリプトを作成することはできません。


もっと良い方法があるはずだ

次のアプローチは非常に不安定で、ユーザーごとに 1 つの SSH セッションに制限されます。ただし、少なくとも 2 つの同時セッションが必要であり、他のユーザーはさらに必要です。しかし、私は試しました...

運が良ければ、1、2羽の鶏を犠牲にして、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/'

  • 最初のセットにはあるが 2 番目のセットにはないすべてのポートは、転送ポートである可能性が高く、実際にセットを減算すると 、41273それぞれ、カップ、パルス、X が生成されます。557106010

  • 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同じプロセスに結び付ける方法が見つかりませんでした。その情報は非ルート ユーザーに公開されません。読み取りたい列にのみが表示されます(この特定のインスタンスの場合)。近いです...557106010sshdnetstat-PID/Program name2339/54

答え1

残念ながら、あなたの質問を以前に見つけられませんでしたが、kamil-maciorowski さんから非常に良い回答をいただきました。

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

要約すると、最初にマスター接続を確立し、それをバックグラウンドで維持してから、ポート転送を要求/設定するため-O *ctl_cmd*に を設定した2 番目のコマンドを発行します。forward

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

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

$portこれにより、ローカル マシン上でバックグラウンドで接続が確立されます。

その後、ローカルで使用する$portか、ssh再度使用してリモート サーバー上でコマンドを実行し、同じ制御ソケットを使用することができます。

フラグについては、次のとおりです。

  • -f= ssh をバックグラウンドに移動するように要求します。
  • -N= リモートコマンドを実行しません。
  • -M= 接続を共有するために、クライアントを「マスター」モードにします。
  • -S= 接続共有用の制御ソケットの場所。
  • -お= アクティブな接続多重化マスタープロセスを制御します。

私の場合は、接続を継続的に確認するために、さらにいくつかの手順を追加しました。

#!/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 接続は必要ありません。この方法では、ログオン バナーと「割り当てられたポート ### ...」テキストがリモート ホストにリダイレクトされます。

ホスト上には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

grepssh 経由で送信する前に、まずローカル側で「割り当てられたポート」メッセージを試しましたが、ssh は stdin でパイプが開くのを待ってブロックするようです。grep は何かを受信するまで書き込み用にパイプを開かないので、基本的にデッドロックが発生します。catただし、同じ動作はないようで、ssh が接続を開くことができるようにすぐに書き込み用にパイプを開きます。

これはリモート側でも同じ問題です。なぜreadstdin から grep するのではなく、1 行ずつ行うのでしょうか。そうしないと、ssh トンネルが閉じられるまで `/tmp/allocatedports' が書き出されず、目的が達成されません。

~/getsshport.shコマンドを指定しないと、バナー テキストやパイプ内のその他の内容がリモート シェルで実行されるため、ssh の stderr を次のようにコマンドにパイプすることが推奨されます。

答え3

テイク2(歴史を参照)SCP-10 ...サーバー側からだと少しシンプルですが、これでうまくいくはずです。要点は次のとおりです。

  1. 環境変数をクライアントからサーバーに渡し、ポート情報が利用可能になったときにそれを検出して取得し、使用する方法をサーバーに伝えます。
  2. ポート情報が利用可能になったら、それをクライアントからサーバーにコピーし、サーバーがそれを取得できるようにして(上記のパート1の助けを借りて)、それを使用します。

まず、リモート側で設定し、環境変数の送信を有効にする必要があります。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. リダイレクト中にファイル名を環境変数としてサーバーに渡すsshstderrからファイルへ
  4. バックグラウンドジョブは、別の方法を使用してstderr一時ファイルをサーバー側にコピーします。SCP-10 ...
  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

注記: 上記のコードは、もちろん十分にテストされておらず、あらゆる種類のバグやコピー&ペーストエラーなどが含まれている可能性があります。これを使用する人は、それを理解しておく方が良いでしょう。自己責任!私はローカルホスト接続のみを使用してテストしましたが、私のテスト環境では動作しました。結果は異なる場合があります。

答え4

これは扱いにくいもので、SSH_CONNECTIONまたは のDISPLAYような追加のサーバー側処理があればすばらしいのですが、追加するのは簡単ではありません。問題の一部は、sshクライアントのみがローカルの宛先を認識し、(サーバーへの) 要求パケットにはリモート アドレスとポートのみが含まれていることです。

ここでの他の回答には、クライアント側をキャプチャしてサーバーに送信するためのさまざまな見苦しい解決策があります。ここでは、正直言ってそれほど見苦しいわけではない別のアプローチを紹介しますが、少なくともこの見苦しい部分はクライアント側で抑えられます ;-)

  • クライアント側で、SendEnvいくつかの環境変数をネイティブに ssh 経由で送信できるように追加/修正します (おそらくデフォルトではありません)
  • サーバー側で、AcceptEnv同じものを受け入れるように追加/修正します(デフォルトではおそらく有効になっていません)
  • ssh動的にロードされたライブラリを使用してクライアントのstderr出力を監視し、sshクライアント環境を更新します。接続設定中
  • プロファイル/ログインスクリプトでサーバー側の環境変数を取得します。

これは、環境が交換される前にリモート転送が設定され記録されるため( で確認)、(今のところは幸いにも)機能します。動的にロードされるライブラリは、libc 関数(→ → → )ssh -vv ...をキャプチャする必要があります。ELF バイナリで関数をリダイレクトまたはラップする(再コンパイルせずに)ことは、動的ライブラリの関数に対して同じことを行うよりも桁違いに複雑です。write()ssh_confirm_remote_forward()logit()do_log()write()

クライアント.ssh/config(またはコマンドライン-o SendEnv ...

Host somehost
  user whatever
  SendEnv SSH_RFWD_*

サーバー上sshd_config(ルート/管理者の変更が必要)

AcceptEnv LC_* SSH_RFWD_*

このアプローチは Linux クライアントで機能し、サーバー上で特別な操作は必要ありません。若干の調整を加えるだけで他の *nix でも機能するはずです。少なくとも OpenSSH 5.8p1 から 7.5p1 までで機能します。

コンパイルしてgcc -Wall -shared -ldl -Wl,-soname,rfwd -o rfwd.so rfwd.c 次のように呼び出します:

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

注意:

  • コードにはエラーチェックがほとんどありません
  • 環境を変える5月スレッド関連の問題を引き起こす、PAMはスレッドを使用する、問題は予想していないが、テストしていない
  • ssh現時点では、* local:port:remote:port* 形式の完全な転送は明確にログに記録されません (必要な場合は、debug1メッセージのさらなる解析ssh -vが必要になります) が、これは使用例では必要ありません。

奇妙なことに、OpenSSH にはポート転送に関する情報を取得する手段がないようです。

エスケープを使ってこれを対話的に(部分的に)行うことができますが~#、奇妙なことに実装ではリッスン中のチャネルをスキップし、開いている(つまりTCP ESTABLISHED)チャネルのみをリストし、いずれの場合も有用なフィールドを印刷しません。channels.c channel_open_message()

その関数にパッチを当ててスロットの詳細を印刷することはできますSSH_CHANNEL_PORT_LISTENERが、それではローカル転送しか取得できません(チャンネル実際のものとは異なります転送)。または、パッチを適用して、グローバルoptions構造体から 2 つの転送テーブルをダンプすることもできます。

#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の場合は、もう1つのオプションがあります。これは私が通常使用するオプションですが、リモートではなくローカル転送用です。127.0.0.1 lo/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 序数ニーモニック) は、1 日の終わりに混乱を解消するのに役立ちます。

関連情報