У меня было подобное требование пару лет назад. Я решил это с помощью следующего скрипта.
#!/bin/bash
# This script is designed to be scheduled by cron as often as is required
# If STARTFILE exists, it will start/restart the connection
# If STOPFILE exists, it will stop the connection
# If PORTFILE exist, makes sure the tunnel is started, to survive restarts
#
# PORTFILE will always contain the port number on the remote host that the SSH connection is tunneled to
#
# This utilizes the control socket option of SSH to control the tunnel
# Base name, used by other variables
NAME="ssh_tunnel"
# Create this file to start the server
STARTFILE="/etc/$NAME/$NAME.start"
# This file will contain the port number on the remote server to connect to to access the tunnel
PORTFILE="/etc/$NAME/$NAME.port"
# Create this file to stop the server
STOPFILE="/etc/$NAME/$NAME.stop"
# The user and host to connect the tunnel to
REMOTE="user@hostname"
# The private key of the user on the remote server to create the tunnel to
KEYFILE="/etc/$NAME/.ssh/$NAME"
# The control socket of the SSH connection
SOCKET="/var/run/$NAME.socket"
# First port to try and listen on at remote host
LISTEN=9000
# Last port to try and listen on at remote host
MAXPORT=9999
SSH=$(which ssh)
# We need to run as root, otherwise it will fail
if [ "$(id -u)" != "0" ]; then
echo "Must be run as root!"
exit 1
fi
# Make directory if if doesn't exist
if [ ! -d "/etc/$NAME" ]; then
mkdir "/etc/$NAME"
fi
# Starts the tunnel and updates the control files
start_tunnel() {
# Remove port file, since it is outdated, if it exists
if [ -f ${PORTFILE} ]; then
rm -f ${PORTFILE}
fi
# Start tunnel and wait 2 seconds.. It the tunnel isn't up, then the port is busy (or the public key is foobar)
while true; do
${SSH} -M -S ${SOCKET} -2 -4 -C -f -N -i ${KEYFILE} -o CheckHostIP=no -o KeepAlive=yes -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o BatchMode=yes ${REMOTE} -R $LISTEN:localhost:22
sleep 2
check_tunnel && break
set LISTEN=LISTEN+1
if [ $LISTEN -eq $MAXPORT ]; then
# No ports available (or more likely, the public key is incorrect)
exit 1
fi
done
echo ${LISTEN} > ${PORTFILE}
# Remove startfile, since the process is now started
if [ -f ${STARTFILE} ]; then
rm -f ${STARTFILE}
fi
}
# Stops the tunnel and cleans up the control files
stop_tunnel() {
# Remove portfile and stopfile if they exist
if [ -f ${PORTFILE} ]; then
rm -f ${PORTFILE}
fi
if [ -f ${STOPFILE} ]; then
rm -f ${STOPFILE}
fi
${SSH} -S ${SOCKET} -O exit ${REMOTE} > /dev/null 2>&1
}
# Check if the tunnel is up
check_tunnel() {
if [ -e ${SOCKET} ]; then
(${SSH} -S ${SOCKET} -O check ${REMOTE} 2>&1 | grep -q "running") && return 0
fi
return 1
}
# Use a lock file so only one instance is running
(
flock -n 9 || exit 1
if [ -f ${STARTFILE} ]; then
# Restart if running, otherwise just start
check_tunnel && stop_tunnel
start_tunnel
elif [ -f ${STOPFILE} ]; then
# Stop if running
check_tunnel && stop_tunnel
elif [ -f ${PORTFILE} ]; then
# The tunnel should be running, might not be after a reboot, for example
check_tunnel || start_tunnel
fi
) 9>/var/run/$NAME.lock
Скрипт предназначен для запланированной работы cron (в моем случае, каждую минуту).
Он проверяет наличие файла, указанного в переменной STARTFILE
. Это было создано с помощью веб-интерфейса, но этот скрипт должен быть довольно легко модифицирован в соответствии с вашими потребностями. Причина контроля веб-интерфейса заключается в том, что клиент, возможно, не хочет иметь постоянный бэкдор в своей сети ... :) К сожалению, я не могу поделиться веб-частью, так как это часть гораздо более крупного проекта.
В любом случае, для того, чтобы это работало, вы должны настроить переменные REMOTE
и KEYFILE
чтобы они указывали на действительного пользователя / хоста и закрытый ключ. Остальное довольно необязательно.
После этого просто создайте STARTFILE
и запустите скрипт (или запланируйте его через cron), и туннель должен запуститься.
Предположим, что вы запускаете скрипт на сервере A
, а для REMOTE
задано значение user@B
Если содержимое PORTFILE
на A
равно 9000
, вы должны иметь возможность войти на сервер A
через B:9000
, используя любого действительного пользователя с сервера A
Я надеюсь, что все это имеет смысл ..