Skip to content

Instantly share code, notes, and snippets.

@pyhedgehog
Last active April 8, 2020 02:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyhedgehog/f64df4cb4e2abf4cbb240edfc8aafd88 to your computer and use it in GitHub Desktop.
Save pyhedgehog/f64df4cb4e2abf4cbb240edfc8aafd88 to your computer and use it in GitHub Desktop.
script to allow autossh switch between several hosts

Problem

If you want to connect to some ssh server for port forwarding you use autossh to keep it alive disregarding network issues.

It you want to switch between several unreliable tcp ports you can use balance to choose between them.

But when you want to switch between several unreliable ssh servers, you can't direct your ssh client to balance because different servers will have different hostkeys and using single config entry for them will ends in either verification errors or in insecure configuration accepting anybody in the middle.

Solution

You need to switch host parameter to ssh but keep other parameters from autossh intact. Here steps-in this script. You should define AUTOSSH_PATH=ssh_failover to force autossh use this script instead of direct call of ssh command.

Caveats

I was too lazy to write complete options parser, so I split options and list of hosts by last non-option argument. Then -- argument splits list of hosts from remote command passed to ssh.

Proposed configuration

ssh_config

Host server1 server1-autossh
  HostName server1.example.com
  User user1
Host server2 server2-autossh
  HostName server2.example.com
  User user2
Host server3 server3-autossh
  HostName server3.example.com
  User user3
Host server1-autossh server2-autossh server3-autossh
  LocalForward 1234 remote-hidden-server1:1234
  LocalForward 2345 remote-hidden-server2:2345

autossh command

# AUTOSSH_PATH=ssh_failover.sh autossh -M $((RANDOM%10000+10000)) -T -N server1-autossh server2-autossh server3-autossh
#!/bin/bash
debug=false
SSH_OPTION=
SSH_HOSTS=
SSH_COMMAND=
cmd=false
prev=
for i in `seq $#` ; do
if $cmd ; then
SSH_COMMAND="$SSH_COMMAND '${!i}'"
else
case "${!i}" in
--)
cmd=true
;;
-*)
if [[ -n "$SSH_HOSTS" ]] ; then
SSH_OPTION="$SSH_OPTION $SSH_HOSTS"
SSH_HOSTS=
fi
SSH_OPTION="$SSH_OPTION '${!i}'"
;;
*)
case "$prev" in
-[iLR])
SSH_OPTION="$SSH_OPTION '${!i}'"
;;
*)
SSH_HOSTS="$SSH_HOSTS '${!i}'"
;;
esac
;;
esac
prev="${!i}"
fi
done
if $debug ; then
echo "SSH_OPTION=$SSH_OPTION"
echo "SSH_HOSTS=$SSH_HOSTS"
echo "SSH_COMMAND=$SSH_COMMAND"
fi
eval "hosts=($SSH_HOSTS)"
host_num=${#hosts[*]}
if [[ $host_num == 0 ]] ; then
echo "Error: No ssh hosts specified.">&2
exit 1
fi
#eval "vargs $SSH_HOSTS"
#echo "hosts#=$host_num"
#echo "hosts0=${hosts[0]}"
#echo "hosts1=${hosts[1]}"
#echo "hosts$((host_no-1))=${hosts[host_no-1]}"
##echo "hosts$host_no=${hosts[host_no]}"
##echo "hosts$((host_no+1))=${hosts[host_no+1]}"
key=$(cksum <<EOF
SSH_OPTION=$SSH_OPTION
SSH_HOSTS=$SSH_HOSTS
SSH_COMMAND=$SSH_COMMAND
EOF
)
key="${SSH_FAILOVER_KEY:-${key% *}}"
[[ -d /var/run/ssh_failover ]] || mkdir -p /var/run/ssh_failover
if [[ -n $SSH_FAILOVER_KEY || ! -f /var/run/ssh_failover/${key}.txt ]] ; then
cat >/var/run/ssh_failover/${key}.txt <<EOF
SSH_OPTION=$SSH_OPTION
SSH_HOSTS=$SSH_HOSTS
SSH_COMMAND=$SSH_COMMAND
EOF
fi
if [[ -f /var/run/ssh_failover/${key} ]] ; then
host_no="$(cat "/var/run/ssh_failover/${key}")"
else
host_no=0
fi
$debug && echo "host_no(in)=$host_no"
host="${hosts[host_no]}"
$debug && echo "host=$host"
host_no=$((host_no+1))
if [[ host_no -ge host_num ]] ; then
host_no=0
fi
$debug && echo "host_no(out)=$host_no"
echo -n "$host_no">"/var/run/ssh_failover/${key}"
$debug && eval "vargs $SSH_OPTION '$host' $SSH_COMMAND"
eval "exec ssh $SSH_OPTION '$host' $SSH_COMMAND"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment