20

I have my ~/.ssh/config configured with various hosts that are accessible either while on our company VPN, or via a SSH proxy server.

At the moment I just have

Host internal-server
    ProxyCommand ssh -W internal.ip:22 external-server

However if I'm in the internal network I can directly access the internal ip, so proxying through the external server just adds a delay to connecting.

Is there a way I can provisionally proxy if the internal ip isn't reachable, and connect directly otherwise?

9 Answers 9

10

I usually setup something like this. It assumes the intermediate host will be able to resolve the name.

Host *%homeproxy
    ProxyCommand ssh user@proxyhost /bin/netcat -w 1 $(echo %h | cut -d%% -f1) 22

So I would connect to like ssh blah%homeproxy.

4
  • My google-fu is failing me. Can you explain this more? I've never seen this *% syntax before.
    – fukawi2
    Commented Sep 4, 2013 at 1:41
  • 3
    There is nothing special about the % character. You could use anything really. The percent is used since it isn't valid in a hostname, or username. The cut command withing the ProxyCommand uses the % as a delimiter to pull out the hostname.
    – Zoredache
    Commented Sep 4, 2013 at 1:44
  • 1
    This is good, however it does mean that I have to spell out the full hostname rather than the ssh alias.
    – alt
    Commented Sep 5, 2013 at 0:22
  • 1
    @mobiusnz It actually means simply that you need a different name for each different means of connecting. In my case, for example, if I'm at home, I use ssh targetH, meaning target from home. If in a local network to target, simply ssh target. It'd've been nice to have this stated more straightforwardly in the answer though.
    – Rubens
    Commented Jan 8, 2015 at 16:53
7

I use a different method using Match. In my case, I want to use a local proxy if it's running, or not, if it isn't. The following directive accomplishes this nicely:

Match Exec "nc -z 127.0.0.1 1086"
    ProxyCommand nc -X 5 -x 127.0.0.1:1086 %h %p

It matches every ssh attempt, and executes a quick check using nc to see if my proxy is up and running on 1086 (in which case, nc exits with no error). If that happens, it sets the ProxyCommand.

2
  • That's good, but it sets the proxy for all requests. Is there any way to set a proxy only for some hosts?
    – CLOVIS
    Commented Jul 8, 2020 at 14:43
  • 1
    @CLOVIS - you can run a bash script instead of just the bare netcat command, and do whatever conditional logic you wanted in there before calling nc at the end. Commented Jul 10, 2020 at 5:59
4

Thanks for @till's Answer, it inspired me a lot.

I found that you can forcely redirect your connection with ProxyCommand nc dst dst-port.

For example, You will in fact connect to B.com if you use

ssh A.com -o ProxyCommand="nc B.com 22"

But UserKnownHostsFile will still record as A.com

So you could add a domain "auto" to your ssh_config

Host auto.internal-server
  Hostname {internal-server ip or domain}
  ProxyCommand bash -c '(timeout 0.1 nc -z %h %p) && nc %h %p || ssh -W %h:%p external-server'

I replaced nc -w 1 %h %p with (timeout 0.1 nc -z %h %p) && nc %h %p , it will be more quick if you could reach internal-server less then 100ms.

Or you can replaced by ping, but it might indicate bad information if you use a TCP based proxy like proxychains, or server doesn't allow a ICMP echo.

Host auto.internal-server
  Hostname {internal-server ip or domain}
  ProxyCommand bash -c '(ping %h &>/dev/null) && nc %h %p || ssh -W %h:%p external-server'

You can replace (timeout 0.1 nc -z %h %p) with anything which detects whether you are in internal-server.

If you has multiple candidate IPs, even you can use this:

Host auto.internal-server
  Hostname {internal-server ip or domain}
  ProxyCommand bash -c 'f(){(timeout 0.1 ping -c 1 $1 &>/dev/null) && nc $1 %p;}; f 1.1.1.1 || f 2.2.2.2 || f 3.3.3.3'

It will try to connect 1.1.1.1, if fail try to connect 2.2.2.2, and then 3.3.3.3.

2

A decade ago I wrote a proxy command for this kind of scenario. In your usage case my proxy command could be used like this:

Host internal-server
    ProxyCommand ssh-multipath-proxy %h:%p -- ssh -W %h:%p external-server

A couple of caveats: I am a bit embarrassed to admit, that it doesn't handle IPv6, and that it will only try a single IP address per hostname. Also, it won't transfer the client banner to the server before the server banner has been sent. But that is unlikely to cause problems.

1

This works a little bit more automagically with a tiny additional 0.1s delay:

Host proxyhost.example.com
ProxyCommand none

Host *.example.com
ProxyCommand sh -c "socat 2>/dev/null - tcp4:%h:%p,connect-timeout=0.1 || exec ssh -W  %h:%p proxyhost.example.com"
1

(Not enough reputation for a comment, so another answer, which it kind of is, anyway.)

Additionally to domoarigato answer who uses the Match exec to add the proxy command, you may add a Match host there, as well. This way the match execution will only be performed on that host which is quite important because you want a) this proxy only on this host and b) not slow down you ssh connection with this command for all hosts. It also doesn't change the name to be used for ssh which is sooo important for my practical usage: having the same ssh-hostname in script, shortcuts, profiles, etc. I guess that was the main desire from OP (at least it was for me when I stumbled upon this question).

My ssh_config looks like:

Match host targetname !exec "nc -z -w 1 targethostname 22"
ProxyCommand ssh proxyhostname -a -Y  nc -v %h 22

Host targetname
Hostname targethostname
User username
ForwardX11 yes

(notice I also added a -w 1 option to the nc port scan which is a one second timeout because filtered ports will otherwise take this command forever to complete - even 1 second can be tiresome, so any better solutions welcome!)

0

I would typically create another entry something like this --

Host myVM
    ProxyCommand ssh -W internal.ip:22 external-server
    User ubuntu

Host myVM-np
    User ubuntu

Then just invoke the one you want, depending on your current proxy environment.

0

With Match and Exec you can check whether SSH port is accessible, and only use ProxyCommand if not. See my example here: https://stackoverflow.com/questions/40746463/how-to-automatically-switch-ssh-config-based-on-local-subnet/62924483#62924483

0

Another example for Match host ... exec "...": Consider that connecting to internal.example.com requires a bastion/proxy (via ProxyJump) unless you are already connected via VPN. The fragment !exec "host internal.example.com" applies only when internal.example.com cannot be looked up via DNS.

Match host internal.example.com !exec "host internal.example.com"
  ProxyJump bastion.example.com
Host internal.example.com
  User foobar

(NB: also posted to https://wiki.archlinux.org/index.php?title=OpenSSH&diff=764226&oldid=755943)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .