Suppose you have a server in your private home network. It's called myserver.home, and you SSH into it as user me. When you're at home, all you need to get in is:
ssh me@myserver.home
But now you want to SSH from outside. You've set up a dynamic DNS service under the name mydyndns.example.org, and configured forwarding in your router on external port 9000 to 22 on myserver.home. Your command becomes:
ssh me@mydyndns.example.org -p 9000
You can set up aliases for these command in ~/.ssh/config:
Host srv-ext srv-int User me Host srv-int Hostname myserver.home Host srv-ext Port 9000 Hostname mydyndns.example.org
Cool, except you have to think about which one to use, depending on where you are. Well, it might not be that important, as the srv-ext alias will likely also work at home. Nevertheless, it would be cooler to use just one alias srv, and have it automatically detect whether a local connection is possible. It might also be noticeably faster in some cases.
Solution
SSH configuration provides a Match
directive that can detect several conditions, including the result of a generic command.
You can use that to override the general case of external access with optimizations for internal access.
Detecting the local network
First, you need a way to detect your local network. Perhaps the most robust way is to detect your router's MAC address xx:xx:xx:xx:xx:xx, which you can get with:
$ arp -a _gateway _gateway (192.168.1.254) at xx:xx:xx:xx:xx:xx [ether] on wlp4s0
You could wrap up testing for it in a script:
#!/bin/bash # -*- sh-basic-offset: 2; indent-tabs-mode: nil -*- declare -A mac_arg=() while [ $# -gt 0 ] ; do arg="$1" ; shift case "$arg" in (-h|--host) host_arg="$1" ; shift ;; (--host=*) host_arg="${arg#--host=}" ;; (-m|--mac) arg="$1" ; shift arg="${arg,,}" mac_arg["$arg"]=yes ;; (--mac=*) arg="${arg#--mac=}" arg="${arg,,}" mac_arg["$arg"]=yes ;; (-*|+*) printf >&2 '%s: unknown switch: %s\n' "$0" "$arg" exit 1 ;; (*) printf >&2 '%s: unknown arg: %s\n' "$0" "$arg" exit 1 ;; esac done if [ -z "$host_arg" -o "${#mac_arg[@]}" -eq 0 ] ; then printf >&2 'usage: %s -h host -m mac\n' "$0" exit 1 fi read canon ipbr at got_mac rest < <(arp -a "$host_arg" 2> /dev/null) test -n "$got_mac" && test -n "${mac_arg["$got_mac"]}"
(You can surely get away with something a lot simpler.) Drop the script in (say) /usr/local/bin/host-is-mac and make it executable (chmod 755 /usr/local/bin/host-is-mac). Now you can see whether you're in your own network:
$ host-is-mac -h _gateway -m xx:xx:xx:xx:xx && echo internal internal $
If you test a hostname which doesn't resolve, the command quietly fails:
$ host-is-mac -h made-up -m xx:xx:xx:xx:xx && echo internal $
A special case for your home network
Now you can configure SSH to test whether the local network is your home network, to decide whether to connect using internal or external parameters. Conflicting options are resolved by choosing the first instance, so you should define the parameters for external access as the general case, and then precede them with the specific case of being in the same network:
## specific (internal) case Match originalhost="srv" exec "host-is-mac -h _gateway -m xx:xx:xx:xx:xx" Port 22 Hostname myserver.home ## general (external) case Host srv User me HostName mydyndns.example.org Port 9000
The Match
directive enables its following directives only if srv is given as the SSH destination, and then only if the host-is-mac command succeeds.
They are treated as a logical AND with short-circuiting, so the external command is only invoked when necessary.
Test the configuration by using ssh -v srv echo yes, and look for lines with Connecting to in them. If you're connecting locally, you'll see:
debug1: Connecting to myserver.home [192.168.1.100] port 22.
If you're connecting from outside:
debug1: Connecting to mydyndns.example.org [10.10.10.10] port 9000.
Proxying
Suppose you use myserver.home as a proxy to another server otherserver.home, and you want the alias alt-srv to conditionally go direct when local. Its specialization must disable proxying:
Match originalhost="alt-srv" exec "host-is-mac -h _gateway -m xx:xx:xx:xx:xx" ProxyJump none Host alt-srv User me Hostname otherserver.home ProxyJump srv
Handling multiple aliases
If you have several aliases for a single server, you can list them in the Host
clause, separating them with spaces.
However, to list them in the originalhost
condition, separate them with commas:
Match originalhost="srv1,srv2" exec "host-is-mac -h _gateway -m xx:xx:xx:xx:xx" Port 22 Hostname myserver.home Host srv1 srv2 User me HostName mydyndns.example.org Port 9000
You could, of course, just use Match
for both clauses.
Things that didn't work
Trying to make transclusion of configuration files conditional doesn't work:
Match originalhost="srv,alt-srv" exec "host-is-mac -h _gateway -m xx:xx:xx:xx:xx" Include site1-specializations.conf
The Include
directive applies unconditionally, and the Match
directive applies only to the initial directives of the transcluded file, up until the next Match
or Host
.