กำหนดพอร์ตที่จัดสรรแบบไดนามิกสำหรับ OpenSSH RemoteForward


13

คำถาม (TL; DR)

เมื่อกำหนดพอร์ตแบบไดนามิกสำหรับการส่งต่อระยะไกล ( -Rตัวเลือกaka ) สคริปต์บนเครื่องระยะไกล (ตัวอย่างเช่นที่มาจาก.bashrc) จะกำหนดว่าพอร์ตใดถูกเลือกโดย OpenSSH


พื้นหลัง

ฉันใช้ OpenSSH (ทั้งสองด้าน) เพื่อเชื่อมต่อกับเซิร์ฟเวอร์กลางของเราที่ฉันแบ่งปันกับผู้ใช้อื่น ๆ สำหรับเซสชันระยะไกลของฉัน (ตอนนี้) ฉันต้องการส่งต่อ X, ถ้วยและ pulseaudio

สิ่งที่สำคัญที่สุดคือการส่งต่อ X โดยใช้-Xตัวเลือก ที่อยู่ X ที่จัดสรรจะถูกเก็บไว้ในตัวแปรสภาพแวดล้อมDISPLAYและจากนั้นฉันสามารถกำหนดพอร์ต TCP ที่สอดคล้องกันได้ในกรณีส่วนใหญ่อย่างไรก็ตาม DISPLAYแต่ผมแทบจะไม่เคยต้องเพราะเกียรตินิยม Xlib

ฉันต้องการกลไกที่คล้ายกันสำหรับถ้วยและพูลซีดิโอ พื้นฐานสำหรับบริการทั้งสองมีอยู่ในรูปแบบของตัวแปรสภาพแวดล้อม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 จะจัดสรรพอร์ตสำหรับถ้วยและการส่งต่อพัลส์แบบไดนามิก

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ข้อมูลจนสามารถอ่านได้จากบันทึก สิ่งนี้ไม่สามารถใช้งานได้เนื่องจากข้อมูลนั้นเปิดเผยข้อมูลมากกว่าที่จะทำให้ผู้ใช้ที่ไม่ใช่รูทเข้าถึงได้ง่าย

ฉันกำลังคิดเกี่ยวกับการปรับปรุง OpenSSH เพื่อรองรับลำดับ escape เพิ่มเติมซึ่งพิมพ์การแสดงที่ดีของ struct ภายในpermitted_opensแต่ถึงแม้ว่านั่นคือสิ่งที่ฉันต้องการฉันยังคงไม่สามารถสคริปต์เข้าถึงไคลเอนต์ escape sequences จากฝั่งเซิร์ฟเวอร์


จะต้องมีวิธีที่ดีกว่า

วิธีการต่อไปนี้ดูเหมือนไม่เสถียรและถูก จำกัด ให้หนึ่งเซสชัน 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เป็นท่าเรือถ้วยเพราะผลตอบแทนlpstat -h localhost:41273 -a0
  • 55710เป็นพอร์ตชีพจรเพราะผลตอบแทนpactl -s localhost:55710 stat 0(มันจะพิมพ์ชื่อโฮสต์ของลูกค้าของฉันด้วย!)

(ในการทำ substraction I sort -uและเก็บเอาท์พุทจากบรรทัดคำสั่งด้านบนและใช้commในการทำ substraction)

Pulseaudio ช่วยให้ฉันระบุลูกค้าและสำหรับทุกเจตนารมณ์และวัตถุประสงค์นี้อาจทำหน้าที่เป็นจุดยึดเพื่อแยกเซสชัน SSH ที่ต้องแยก แต่ผมยังไม่ได้พบวิธีที่จะผูก41273, 55710และ6010เดียวกันsshdกระบวนการ netstatจะไม่เปิดเผยข้อมูลนั้นแก่ผู้ใช้ที่ไม่ใช่รูท ฉันเพิ่งได้รับ-ในPID/Program nameคอลัมน์ที่ฉันต้องการอ่าน2339/54(ในตัวอย่างนี้) เฉียดฉิว ...


fwiw มีความแม่นยำมากกว่าที่จะบอกว่าnetstatจะไม่แสดง PID สำหรับกระบวนการที่คุณไม่ได้เป็นเจ้าของหรือที่เป็นพื้นที่เคอร์เนล ตัวอย่างเช่น
Bratchley

วิธีที่มีประสิทธิภาพที่สุดคือการแก้ไข sshd ... แพทช์ที่รวดเร็วและสกปรกจะเป็นเพียงไม่กี่บรรทัดที่เซิร์ฟเวอร์ได้รับพอร์ตในระบบจากระบบปฏิบัติการเขียนหมายเลขพอร์ตไปยังไฟล์ชื่อที่สร้างจากผู้ใช้โฮสต์ระยะไกลและ ท่าเรือ. สมมติว่าเซิร์ฟเวอร์รู้พอร์ตในฝั่งไคลเอ็นต์ซึ่งไม่แน่ใจอาจไม่น่าเป็นไปได้ (มิฉะนั้นคุณสมบัติจะมีอยู่แล้ว)
hyde

@hyde: แน่นอน เซิร์ฟเวอร์ระยะไกลไม่ทราบเกี่ยวกับพอร์ตที่ส่งต่อ มันสร้างซ็อกเก็ตการฟังเพียงเล็กน้อยและข้อมูลจะถูกส่งต่อผ่านการเชื่อมต่อ ssh ไม่ทราบเกี่ยวกับพอร์ตปลายทางในเครื่อง
Bananguin

คำตอบ:


1

ใช้สอง (ดูประวัติสำหรับเวอร์ชันที่ทำ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. สร้างชื่อไฟล์ temp สำหรับการเปลี่ยนเส้นทาง stderr
  2. ออกจากงานพื้นหลังเพื่อรอให้ไฟล์มีเนื้อหา
  3. ส่งชื่อไฟล์ไปยังเซิร์ฟเวอร์เป็นตัวแปร env ขณะที่เปลี่ยนเส้นทางssh stderr ไปยังไฟล์
  4. งานพื้นหลังดำเนินการเพื่อคัดลอกไฟล์ stderr temp ไปยังฝั่งเซิร์ฟเวอร์โดยใช้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 และทำงานได้สำหรับฉันในการทดสอบ env YMMV


หลักสูตรใดที่ฉันต้องการscpจากด้านไกลถึงด้านข้างซึ่งไม่สามารถทำได้ ฉันมีวิธีการที่คล้ายกัน แต่ฉันจะปิดแบ็คsshกราวน์หลังจากสร้างการเชื่อมต่อแล้วส่งไฟล์นั้นจากโลคัลไปยังรีโมตผ่านscpจากนั้นดึงsshไคลเอ็นต์ไปยังส่วนหน้าและรันสคริปต์บนรีโมต ฉันไม่ได้คิดวิธีการเขียนสคริปต์พื้นหลังและเบื้องหน้ากระบวนการในท้องถิ่นและระยะไกลอย่าง การห่อและการรวมsshไคลเอ็นต์โลคัลกับสคริปต์รีโมตบางอย่างเช่นนั้นดูเหมือนจะไม่เป็นวิธีที่ดี
Bananguin

อา. ฉันคิดว่าคุณควรพื้นหลังscpฝั่งไคลเอ็นต์เท่านั้น: (while [ ... ] ; do sleep 1 ; done ; scp ... )&. จากนั้นรอที่พื้นหน้าในเซิร์ฟเวอร์.bashrc(สมมติว่าไคลเอนต์ส่งตัวแปร env ที่ถูกต้อง) เพื่อให้ไฟล์ปรากฏ ฉันจะอัปเดตคำตอบในภายหลังหลังจากการทดสอบบางอย่าง (อาจไม่มีเวลาจนกระทั่งพรุ่งนี้)
hyde

@Bananguin เวอร์ชั่นใหม่เสร็จแล้ว ดูเหมือนว่าจะทำงานให้ฉันดังนั้นควรปรับให้เข้ากับกรณีการใช้งานของคุณ เกี่ยวกับ "วิธีการที่ดี" ใช่ แต่ฉันไม่คิดว่าจะมีวิธีการที่ดีที่นี่ ข้อมูลจะต้องผ่านอย่างใดและมันจะเป็นแฮ็คเสมอเว้นแต่คุณจะแก้ไขทั้งไคลเอ็นต์ ssh และเซิร์ฟเวอร์เพื่อให้มันผ่านการเชื่อมต่อเดียว
hyde

และฉันคิดเกี่ยวกับการอัพเดท openssh มากขึ้นเรื่อย ๆ ดูเหมือนจะไม่เป็นเรื่องใหญ่ ข้อมูลมีอยู่แล้ว ฉันแค่ต้องส่งมันไปยังเซิร์ฟเวอร์ เมื่อใดก็ตามที่เซิร์ฟเวอร์ได้รับข้อมูลดังกล่าวเซิร์ฟเวอร์จะทำการเขียนข้อมูลไปที่~/.ssh-${PID}-forwards
Bananguin

1

ตัวอย่างสำหรับฝั่งท้องถิ่นเหมาะสำหรับ. 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

0

ฉันประสบความสำเร็จเช่นเดียวกันโดยการสร้างไปป์บนโลคัลไคลเอ็นต์จากนั้นเปลี่ยนทิศทาง stderr ไปยังไพพ์ซึ่งถูกเปลี่ยนทิศทางไปยังอินพุตของ ssh ด้วย ไม่จำเป็นต้องใช้การเชื่อมต่อหลาย ssh เพื่อสันนิษฐานพอร์ตที่รู้จักฟรีซึ่งอาจล้มเหลว วิธีนี้จะมีการเปลี่ยนเส้นทางแบนเนอร์เข้าสู่ระบบและข้อความ "จัดสรรพอร์ต ### ... " ไปยังโฮสต์ระยะไกล

ฉันมีสคริปต์ง่ายๆบนโฮสต์getsshport.shซึ่งทำงานบนโฮสต์ระยะไกลซึ่งอ่านอินพุตที่เปลี่ยนเส้นทางและแยกวิเคราะห์พอร์ต ตราบใดที่สคริปต์นี้ยังไม่จบลง ssh remote remote จะยังคงเปิดอยู่

ด้านท้องถิ่น

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 จึงได้รับ piped to 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 / allocports 'จะไม่ถูกเขียนออกมาจนกว่าอุโมงค์ ssh จะปิดซึ่งเอาชนะวัตถุประสงค์ทั้งหมด

แนะนำให้ใช้การวาง stderr ของ ssh ลงในคำสั่งเช่น~/getsshport.shโดยไม่ต้องระบุคำสั่งข้อความแบนเนอร์หรืออะไรก็ตามที่อยู่ในไพพ์จะถูกเรียกใช้งานบนรีโมตเชลล์


ดี ฉันเพิ่มrenice +10 $$; exec catก่อนที่doneจะบันทึกทรัพยากร
Spongman

0

นี่เป็นเรื่องยุ่งยากการจัดการฝั่งเซิร์ฟเวอร์พิเศษตามแนว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(จำเป็นต้องเปลี่ยนรูท / การดูแลระบบ)

AcceptEnv LC_* SSH_RFWD_*

วิธีนี้ใช้ได้กับไคลเอนต์ Linux และไม่ต้องการอะไรเป็นพิเศษบนเซิร์ฟเวอร์มันควรใช้กับ * ระวังอื่น ๆ ที่มีการปรับแต่งเล็กน้อย ทำงานได้ตั้งแต่อย่างน้อย 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

คำเตือน:

  • ไม่มีการตรวจสอบข้อผิดพลาดในรหัสมากนัก
  • การเปลี่ยนสภาพแวดล้อมอาจทำให้เกิดปัญหาเกี่ยวกับเธรด PAM ใช้เธรดฉันไม่คาดหวังปัญหา แต่ยังไม่ได้ทดสอบ
  • sshขณะนี้ไม่ได้บันทึกการส่งต่ออย่างสมบูรณ์ของแบบฟอร์ม * ภายใน: พอร์ต: ระยะไกล: พอร์ต * (หากจำเป็นต้องแยกวิเคราะห์debug1ข้อความด้วยเพิ่มเติมssh -vจะต้อง) แต่คุณไม่จำเป็นต้องใช้กรณีการใช้งานของคุณ

OpenSSH ผิดปกติพอดูเหมือนจะไม่มีวิธีดึงข้อมูลเกี่ยวกับการส่งต่อพอร์ต

คุณสามารถ (บางส่วน) ทำสิ่งนี้แบบโต้ตอบกับการหลบหลีก~#การใช้งานแปลก ๆ ข้ามช่องทางที่กำลังฟังอยู่จะแสดงเฉพาะรายการที่เปิด (เช่น 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 แบบกำหนดตายตัวบนแพลตฟอร์มส่วนใหญ่

octets ที่เลือกอย่างชาญฉลาด (ตัวช่วยจำ ASCII ลำดับในกรณีของฉัน) ช่วยแก้ปัญหายุ่งเหยิงในตอนท้ายของวัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.