OpenSSH RemoteForward에 대해 동적으로 할당된 포트 결정

OpenSSH RemoteForward에 대해 동적으로 할당된 포트 결정

질문(TL;DR)

원격 전달( -R옵션이라고도 함)을 위해 동적으로 포트를 할당할 때 원격 시스템의 스크립트(예: 에서 소스 .bashrc)가 OpenSSH에서 선택한 포트를 어떻게 확인할 수 있습니까?


배경

저는 OpenSSH(양쪽 끝)를 사용하여 다른 여러 사용자와 공유하는 중앙 서버에 연결합니다. (지금은) 원격 세션의 경우 X, cup 및 pulseaudio를 전달하고 싶습니다.

가장 간단한 것은 -X옵션을 사용하여 X를 전달하는 것입니다. 할당된 X 주소는 환경 변수에 저장되며 DISPLAY이를 통해 대부분의 경우 해당 TCP 포트를 결정할 수 있습니다. 하지만 Xlib는 DISPLAY.

컵과 펄스오디오에도 비슷한 메커니즘이 필요합니다. 두 서비스의 기본 사항은 각각 환경 변수 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.

우리는 포트 전달을 많이 사용하므로 동적 포트 할당이 필요합니다. 정적 포트 할당은 옵션이 아닙니다.

0OpenSSH에는 원격 전달을 위한 바인딩 포트( -R옵션) 를 지정하여 원격 서버에서 동적 포트 할당을 위한 메커니즘이 있습니다 . 다음 명령을 사용하여 OpenSSH는 컵 및 펄스 전달을 위한 포트를 동적으로 할당합니다.

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_SERVER하기 위해 해당 정보를 쉘 스크립트에 저장하려면 어떻게 해야 합니까 ?PULSE_SERVER


막다른 골목

내가 찾을 수 있는 유일한 쉬운 점은 sshd로그에서 해당 정보를 읽을 수 있을 때까지 자세한 내용을 늘리는 것이었습니다. 해당 정보는 루트가 아닌 사용자가 액세스할 수 있는 것보다 훨씬 더 많은 정보를 공개하므로 이는 실행 가능하지 않습니다.

내부 struct 의 멋진 표현을 인쇄하는 추가 이스케이프 시퀀스를 지원하기 위해 OpenSSH를 패치하는 것에 대해 생각하고 있었지만 permitted_opens그것이 내가 원하는 것이라 하더라도 여전히 서버 측에서 클라이언트 이스케이프 시퀀스에 액세스하는 스크립트를 작성할 수 없습니다.


더 좋은 방법이 있을 거에요

다음 접근 방식은 매우 불안정해 보이며 사용자당 하나의 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, 557106010; 각각 컵, 펄스, X입니다.

  • 6010를 사용하여 X 포트로 식별됩니다 DISPLAY.

  • 41273lpstat -h localhost:41273 -a반환 하기 때문에 컵 포트입니다 0.
  • 55710pactl -s localhost:55710 stat을 반환하기 때문에 펄스 포트입니다 0. (내 클라이언트의 호스트 이름도 인쇄합니다!)

(뺄셈 I을 설정하고 sort -u위 명령줄의 출력을 저장하고 comm빼기를 수행하는 데 사용합니다.)

Pulseaudio를 사용하면 클라이언트를 식별할 수 있으며, 모든 의도와 목적을 위해 이는 분리가 필요한 SSH 세션을 분리하기 위한 앵커 역할을 할 수 있습니다. 그러나 나는 묶는 방법 41273과 같은 과정 55710을 찾지 못했습니다 . 루트가 아닌 사용자에게는 해당 정보를 공개하지 않습니다. 나는 읽고 싶은 열 에서만 (이 특별한 경우) 을 얻습니다 . 너무 가까워 ...6010sshdnetstat-PID/Program name2339/54

답변1

불행히도 이전에는 귀하의 질문을 찾지 못했지만 kamil-maciorowski로부터 정말 좋은 답변을 받았습니다.

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

요약하자면 먼저 마스터 연결을 설정하고 이를 백그라운드에서 유지한 다음 포트 전달을 요청/설정하도록 -O *ctl_cmd*설정된 두 번째 명령을 실행합니다.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에 백그라운드로 이동을 요청합니다.
  • -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

답변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>&3stderr과 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을 사용하는 대신 한 줄씩 실행해야 하는가? 그렇지 않으면 SSH 터널이 닫힐 때까지 `/tmp/allocationports'가 기록되지 않아 전체 목적이 무산됩니다.

ssh의 stderr을 명령으로 파이프하는 것이 ~/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 변수로 서버에 전달합니다.SSH파일에 대한 표준 오류
  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

메모: 위의 코드는 물론 철저하게 테스트되지 않았으며 모든 종류의 버그, 복사-붙여넣기 오류 등이 포함될 수 있습니다. 이를 사용하는 사람이라면 누구나 더 잘 이해할 수 있을 것입니다.자신의 책임하에 사용하십시오!나는 단지 localhost 연결을 사용하여 테스트했고 테스트 환경에서는 나에게 효과적이었습니다. YMMV.

답변4

이것은 까다롭습니다. 추가 서버측 처리는 훌륭할 것이지만 SSH_CONNECTION추가 DISPLAY하기는 쉽지 않습니다. 문제의 일부는 클라이언트만이 ssh로컬 대상을 알고 있고 요청 패킷(서버에 대한)에는 다음이 포함되어 있다는 것입니다. 원격 주소와 포트만 있습니다.

여기에 있는 다른 답변에는 이 클라이언트 측을 캡처하여 서버로 보내기 위한 다양하고 좋지 않은 솔루션이 있습니다. 솔직히 말해서 그다지 예쁘지는 않지만 적어도 이 추악한 파티는 클라이언트 측에 유지되는 대체 접근 방식이 있습니다 ;-)

  • 클라이언트 측에서 SendEnvSSH를 통해 일부 환경 변수를 기본적으로 보낼 수 있도록 추가/수정합니다(기본값은 아닐 수도 있음).
  • 서버 측, 동일한 내용을 허용하도록 추가/수정 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(루트/관리자 변경 필요)

AcceptEnv LC_* SSH_RFWD_*

이 접근 방식은 Linux 클라이언트에 작동하며 서버에 특별한 것이 필요하지 않습니다. 약간의 조정만 하면 다른 *nix에서도 작동합니다. 최소 OpenSSH 5.8p1에서 최대 7.5p1까지 작동합니다.

다음을 사용하여 Invoke 로 컴파일합니다 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에는 포트 전달에 대한 정보를 검색할 수 있는 수단이 없는 것 같습니다.

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인 경우 옵션이 하나 더 있습니다. 이는 제가 일반적으로 사용하는 옵션이지만 원격이 아닌 로컬 전달에 사용됩니다. lo127.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 서수 니모닉)은 하루가 끝날 때 혼란을 해결하는 데 도움이 됩니다.

관련 정보